From c75fd6bc88e1a1826ee24ad2dca821918c35fb36 Mon Sep 17 00:00:00 2001 From: whitequark Date: Wed, 27 May 2015 17:04:18 +0300 Subject: [PATCH 001/369] Replace builtin ast with pythonparser.ast. --- artiq/py2llvm/ast_body.py | 2 +- artiq/py2llvm/fractions.py | 4 ++-- artiq/py2llvm/infer_types.py | 5 +++-- artiq/test/py2llvm.py | 6 +++--- setup.py | 2 +- 5 files changed, 10 insertions(+), 9 deletions(-) diff --git a/artiq/py2llvm/ast_body.py b/artiq/py2llvm/ast_body.py index 3cba3a976..f9c635cb8 100644 --- a/artiq/py2llvm/ast_body.py +++ b/artiq/py2llvm/ast_body.py @@ -1,4 +1,4 @@ -import ast +from pythonparser import ast import llvmlite.ir as ll diff --git a/artiq/py2llvm/fractions.py b/artiq/py2llvm/fractions.py index c2a9730fa..2b3fa6fd4 100644 --- a/artiq/py2llvm/fractions.py +++ b/artiq/py2llvm/fractions.py @@ -1,5 +1,5 @@ import inspect -import ast +from pythonparser import parse, ast import llvmlite.ir as ll @@ -18,7 +18,7 @@ def _gcd(a, b): def init_module(module): - func_def = ast.parse(inspect.getsource(_gcd)).body[0] + func_def = parse(inspect.getsource(_gcd)).body[0] function, _ = module.compile_function(func_def, {"a": VInt(64), "b": VInt(64)}) function.linkage = "internal" diff --git a/artiq/py2llvm/infer_types.py b/artiq/py2llvm/infer_types.py index d02aeef36..7de53bab8 100644 --- a/artiq/py2llvm/infer_types.py +++ b/artiq/py2llvm/infer_types.py @@ -1,11 +1,12 @@ -import ast +import pythonparser.algorithm +from pythonparser import ast from copy import deepcopy from artiq.py2llvm.ast_body import Visitor from artiq.py2llvm import base_types -class _TypeScanner(ast.NodeVisitor): +class _TypeScanner(pythonparser.algorithm.Visitor): def __init__(self, env, ns): self.exprv = Visitor(env, ns) diff --git a/artiq/test/py2llvm.py b/artiq/test/py2llvm.py index 9f2948db8..7f01d983c 100644 --- a/artiq/test/py2llvm.py +++ b/artiq/test/py2llvm.py @@ -1,5 +1,5 @@ import unittest -import ast +from pythonparser import parse, ast import inspect from fractions import Fraction from ctypes import CFUNCTYPE, c_int, c_int32, c_int64, c_double @@ -43,7 +43,7 @@ def _base_types(choice): def _build_function_types(f): return infer_function_types( - None, ast.parse(inspect.getsource(f)), + None, parse(inspect.getsource(f)), dict()) @@ -111,7 +111,7 @@ class CompiledFunction: def __init__(self, function, param_types): module = Module() - func_def = ast.parse(inspect.getsource(function)).body[0] + func_def = parse(inspect.getsource(function)).body[0] function, retval = module.compile_function(func_def, param_types) argvals = [param_types[arg.arg] for arg in func_def.args.args] diff --git a/setup.py b/setup.py index 13f35efd8..44087dc4c 100755 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ from setuptools import setup, find_packages requirements = [ "sphinx", "sphinx-argparse", "pyserial", "numpy", "scipy", "python-dateutil", "prettytable", "h5py", "pydaqmx", "pyelftools", - "quamash", "pyqtgraph" + "quamash", "pyqtgraph", "pythonparser" ] scripts = [ From 74080f2cc66501f807f67ba53d9a7658d1fd720d Mon Sep 17 00:00:00 2001 From: whitequark Date: Fri, 29 May 2015 09:53:00 +0300 Subject: [PATCH 002/369] Update .gitignore. --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 548f72714..a8b35b740 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ __pycache__ *.bin *.elf *.fbi +*.pyc soc/runtime/service_table.h doc/manual/_build /build From abbc87e981e8fc9f066975abb7757788b4bb32fd Mon Sep 17 00:00:00 2001 From: whitequark Date: Fri, 29 May 2015 09:53:24 +0300 Subject: [PATCH 003/369] Add new type inferencer. --- artiq/py2llvm/asttyped.py | 53 +++++++++ artiq/py2llvm/types.py | 189 +++++++++++++++++++++++++++++++ artiq/py2llvm/typing.py | 226 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 468 insertions(+) create mode 100644 artiq/py2llvm/asttyped.py create mode 100644 artiq/py2llvm/types.py create mode 100644 artiq/py2llvm/typing.py diff --git a/artiq/py2llvm/asttyped.py b/artiq/py2llvm/asttyped.py new file mode 100644 index 000000000..21f3b07a1 --- /dev/null +++ b/artiq/py2llvm/asttyped.py @@ -0,0 +1,53 @@ +""" +The typedtree module exports the PythonParser AST enriched with +typing information. +""" + +from pythonparser import ast +from pythonparser.algorithm import Visitor as ASTVisitor + +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) + list of variables resolved as globals + """ + +class ClassDefT(ast.ClassDef, scoped): + pass + +class FunctionDefT(ast.FunctionDef, scoped): + pass + +class LambdaT(ast.Lambda, scoped): + pass + +class DictCompT(ast.DictComp, scoped): + pass + +class ListCompT(ast.ListComp, scoped): + pass + +class SetCompT(ast.SetComp, scoped): + pass + +class argT(ast.arg, commontyped): + pass + +class NumT(ast.Num, commontyped): + pass + +class NameT(ast.Name, commontyped): + pass + +class NameConstantT(ast.NameConstant, commontyped): + pass diff --git a/artiq/py2llvm/types.py b/artiq/py2llvm/types.py new file mode 100644 index 000000000..52cbe7518 --- /dev/null +++ b/artiq/py2llvm/types.py @@ -0,0 +1,189 @@ +""" +The :mod:`types` module contains the classes describing the types +in :mod:`asttyped`. +""" + +import string + +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 < 26: + ident[pos] = string.ascii_lowercase[cur_n + 1] + break + else: + ident[pos] = "a" + pos -= 1 + if pos < 0: + ident = "a" + ident + +class UnificationError(Exception): + def __init__(self, typea, typeb): + self.typea, self.typeb = typea, typeb + + +class Type(object): + pass + +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 + + def find(self): + if self.parent is self: + return self + else: + root = self.parent.find() + self.parent = root # path compression + return root + + def unify(self, other): + other = other.find() + + if self.parent is self: + self.parent = other + else: + self.find().unify(other) + + def __repr__(self): + if self.parent is self: + return "TVar(%d)" % 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.""" + + def __init__(self, name, params={}): + self.name, self.params = name, params + + def find(self): + return self + + def unify(self, other): + 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]) + else: + raise UnificationError(self, other) + + def __repr__(self): + return "TMono(%s, %s)" % (repr(self.name), repr(self.params)) + + def __eq__(self, other): + return isinstance(other, TMono) and \ + self.name == other.name and \ + self.params == other.params + + def __ne__(self, other): + return not (self == other) + +class TTuple(Type): + """A tuple type.""" + + def __init__(self, elts=[]): + self.elts = elts + + def find(self): + return self + + def unify(self, other): + if isinstance(other, TTuple) and len(self.elts) == len(other.elts): + for selfelt, otherelt in zip(self.elts, other.elts): + selfelt.unify(otherelt) + else: + raise UnificationError(self, other) + + def __repr__(self): + return "TTuple(%s)" % (", ".join(map(repr, self.elts))) + + def __eq__(self, other): + return isinstance(other, TTuple) and \ + self.elts == other.elts + + def __ne__(self, other): + return not (self == other) + +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 self != other: + raise UnificationError(self, other) + + def __repr__(self): + return "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 TBool(): + """A boolean type.""" + return TMono("bool") + +def TInt(width=TVar()): + """A generic integer type.""" + return TMono("int", {"width": width}) + +def TFloat(): + """A double-precision floating point type.""" + return TMono("float") + + +class TypePrinter(object): + """ + A class that prints types using Python-like syntax and gives + type variables sequential alphabetic names. + """ + + def __init__(self): + self.gen = genalnum() + self.map = {} + + def name(self, typ): + 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, TMono): + return "%s(%s)" % (typ.name, ", ".join( + ["%s=%s" % (k, self.name(typ.params[k])) for k in typ.params])) + elif isinstance(typ, TTuple): + if len(typ.elts) == 1: + return "(%s,)" % self.name(typ.elts[0]) + else: + return "(%s)" % ", ".join(list(map(self.name, typ.elts))) + elif isinstance(typ, TValue): + return repr(typ.value) + else: + assert False diff --git a/artiq/py2llvm/typing.py b/artiq/py2llvm/typing.py new file mode 100644 index 000000000..effbca78f --- /dev/null +++ b/artiq/py2llvm/typing.py @@ -0,0 +1,226 @@ +from pythonparser import source, ast, algorithm, diagnostic, parse_buffer +from . import asttyped, types + +# This visitor will be called for every node with a scope, +# i.e.: class, function, comprehension, lambda +class LocalExtractor(algorithm.Visitor): + def __init__(self, engine): + super().__init__() + self.engine = engine + + self.in_root = False + self.in_assign = False + self.typing_env = {} + + # 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): + try: + self.in_assign = True + return self.visit(node) + finally: + self.in_assign = False + + def visit_Assign(self, node): + for target in node.targets: + self.visit_in_assign(target) + self.visit(node.value) + + def visit_AugAssign(self, node): + self.visit_in_assign(node.target) + self.visit(node.op) + self.visit(node.value) + + def visit_For(self, node): + self.visit_in_assign(node.target) + self.visit(node.iter) + self.visit(node.body) + self.visit(node.orelse) + + def visit_withitem(self, node): + self.visit(node.context_expr) + if node.optional_vars is not None: + self.visit_in_assign(node.optional_vars) + + def visit_comprehension(self, node): + self.visit_in_assign(node.target) + self.visit(node.iter) + for if_ in node.ifs: + self.visit(node.ifs) + + def visit_root(self, node): + if self.in_root: + return + self.in_root = True + self.generic_visit(node) + + visit_ClassDef = visit_root # don't look at inner scopes + visit_FunctionDef = visit_root + visit_Lambda = visit_root + visit_DictComp = visit_root + visit_ListComp = visit_root + visit_SetComp = visit_root + + def _assignable(self, name): + if name not in self.typing_env and name not in self.nonlocal_: + self.typing_env[name] = types.TVar() + + def visit_arg(self, node): + 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 _check_not_in(self, name, names, curkind, newkind, loc): + if name in names: + diag = diagnostic.Diagnostic('fatal', + "name '{name}' cannot be {curkind} and {newkind} simultaneously", + {"name": name, "curkind": curkind, "newkind": newkind}, loc) + self.engine.process(diag) + + def visit_Global(self, node): + for name, loc in zip(node.names, node.name_locs): + self._check_not_in(name, self.nonlocal_, 'nonlocal', 'global', loc) + self._check_not_in(name, self.params, 'a parameter', 'global', loc) + self.global_.add(name) + + def visit_Nonlocal(self, node): + for name, loc in zip(node.names, node.name_locs): + self._check_not_in(name, self.global_, 'global', 'nonlocal', loc) + self._check_not_in(name, self.params, 'a parameter', 'nonlocal', loc) + self.nonlocal_.add(name) + + def visit_ExceptHandler(self, node): + self.visit(node.type) + self._assignable(node.name) + for stmt in node.body: + self.visit(stmt) + + +class Inferencer(algorithm.Transformer): + def __init__(self, engine): + self.engine = engine + self.env_stack = [{}] + + def _unify(self, typea, typeb, loca, locb): + try: + typea.unify(typeb) + except types.UnificationError as e: + note1 = diagnostic.Diagnostic('note', + "expression of type {typea}", + {"typea": types.TypePrinter().name(typea)}, + loca) + note2 = diagnostic.Diagnostic('note', + "expression of type {typeb}", + {"typeb": types.TypePrinter().name(typeb)}, + locb) + diag = diagnostic.Diagnostic('fatal', + "cannot unify {typea} with {typeb}: {fraga} is incompatible with {fragb}", + {"typea": types.TypePrinter().name(typea), + "typeb": types.TypePrinter().name(typeb), + "fraga": types.TypePrinter().name(e.typea), + "fragb": types.TypePrinter().name(e.typeb),}, + loca, [locb], notes=[note1, note2]) + self.engine.process(diag) + + def visit_FunctionDef(self, node): + extractor = LocalExtractor(engine=self.engine) + extractor.visit(node) + + self.env_stack.append(extractor.typing_env) + node = asttyped.FunctionDefT( + typing_env=extractor.typing_env, globals_in_scope=extractor.global_, + name=node.name, args=self.visit(node.args), returns=self.visit(node.returns), + body=[self.visit(x) for x in 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) + self.generic_visit(node) + self.env_stack.pop() + + return node + + def _find_name(self, name, loc): + for typing_env in reversed(self.env_stack): + if name in typing_env: + return typing_env[name] + diag = diagnostic.Diagnostic('fatal', + "name '{name}' is not bound to anything", {"name":name}, loc) + self.engine.process(diag) + + def visit_arg(self, node): + return asttyped.argT(type=self._find_name(node.arg, node.loc), + arg=node.arg, annotation=self.visit(node.annotation), + arg_loc=node.arg_loc, colon_loc=node.colon_loc, loc=node.loc) + + def visit_Num(self, node): + if isinstance(node.n, int): + typ = types.TInt() + elif isinstance(node.n, float): + typ = types.TFloat() + else: + diag = diagnostic.Diagnostic('fatal', + "numeric type {type} is not supported", node.n.__class__.__name__, + node.loc) + self.engine.process(diag) + return asttyped.NumT(type=typ, + n=node.n, 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_Assign(self, node): + node = self.generic_visit(node) + if len(node.targets) > 1: + self._unify(types.TTuple([x.type for x in node.targets]), node.value.type, + node.targets[0].loc.join(node.targets[-1].loc), node.value.loc) + else: + self._unify(node.targets[0].type, node.value.type, + node.targets[0].loc, node.value.loc) + return node + +class Printer(algorithm.Visitor): + def __init__(self, buf): + self.rewriter = source.Rewriter(buf) + self.type_printer = types.TypePrinter() + + def rewrite(self): + return self.rewriter.rewrite() + + def generic_visit(self, node): + if hasattr(node, 'type'): + self.rewriter.insert_after(node.loc, " : %s" % self.type_printer.name(node.type)) + + super().generic_visit(node) + +def main(): + import sys, fileinput + engine = diagnostic.Engine(all_errors_are_fatal=True) + try: + buf = source.Buffer("".join(fileinput.input()), fileinput.filename()) + parsed = parse_buffer(buf, engine=engine) + typed = Inferencer(engine=engine).visit(parsed) + printer = Printer(buf) + printer.visit(typed) + print(printer.rewrite().source) + except diagnostic.Error as e: + print("\n".join(e.diagnostic.render()), file=sys.stderr) + +if __name__ == "__main__": + main() From 56d1a9bc57f436720ae210e5baa008aabb040da4 Mon Sep 17 00:00:00 2001 From: whitequark Date: Fri, 29 May 2015 10:01:22 +0300 Subject: [PATCH 004/369] Shorten the unification error message when too redundant. --- artiq/py2llvm/typing.py | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/artiq/py2llvm/typing.py b/artiq/py2llvm/typing.py index effbca78f..bc14dfa25 100644 --- a/artiq/py2llvm/typing.py +++ b/artiq/py2llvm/typing.py @@ -129,13 +129,20 @@ class Inferencer(algorithm.Transformer): "expression of type {typeb}", {"typeb": types.TypePrinter().name(typeb)}, locb) - diag = diagnostic.Diagnostic('fatal', - "cannot unify {typea} with {typeb}: {fraga} is incompatible with {fragb}", - {"typea": types.TypePrinter().name(typea), - "typeb": types.TypePrinter().name(typeb), - "fraga": types.TypePrinter().name(e.typea), - "fragb": types.TypePrinter().name(e.typeb),}, - loca, [locb], notes=[note1, note2]) + if e.typea.find() == typea.find() and e.typeb.find() == typeb.find(): + diag = diagnostic.Diagnostic('fatal', + "cannot unify {typea} with {typeb}", + {"typea": types.TypePrinter().name(typea), + "typeb": types.TypePrinter().name(typeb)}, + loca, [locb], notes=[note1, note2]) + else: # give more detail + diag = diagnostic.Diagnostic('fatal', + "cannot unify {typea} with {typeb}: {fraga} is incompatible with {fragb}", + {"typea": types.TypePrinter().name(typea), + "typeb": types.TypePrinter().name(typeb), + "fraga": types.TypePrinter().name(e.typea), + "fragb": types.TypePrinter().name(e.typeb),}, + loca, [locb], notes=[note1, note2]) self.engine.process(diag) def visit_FunctionDef(self, node): From f979a76c7cdb5bcd15db0b1f10df7303c97565e1 Mon Sep 17 00:00:00 2001 From: whitequark Date: Sat, 30 May 2015 08:06:19 +0300 Subject: [PATCH 005/369] Require nonlocal names to be bound in an outer scope. --- artiq/py2llvm/typing.py | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/artiq/py2llvm/typing.py b/artiq/py2llvm/typing.py index bc14dfa25..5e270f052 100644 --- a/artiq/py2llvm/typing.py +++ b/artiq/py2llvm/typing.py @@ -4,8 +4,9 @@ from . import asttyped, types # This visitor will be called for every node with a scope, # i.e.: class, function, comprehension, lambda class LocalExtractor(algorithm.Visitor): - def __init__(self, engine): + def __init__(self, env_stack, engine): super().__init__() + self.env_stack = env_stack self.engine = engine self.in_root = False @@ -103,6 +104,19 @@ class LocalExtractor(algorithm.Visitor): for name, loc in zip(node.names, node.name_locs): self._check_not_in(name, self.global_, 'global', 'nonlocal', loc) self._check_not_in(name, self.params, 'a parameter', 'nonlocal', loc) + + found = False + for outer_env in reversed(self.env_stack): + if name in outer_env: + found = True + break + if not found: + diag = diagnostic.Diagnostic('fatal', + "can't declare name '{name}' as nonlocal: it is not bound in any outer scope", + {"name": name}, + loc, [node.keyword_loc]) + self.engine.process(diag) + self.nonlocal_.add(name) def visit_ExceptHandler(self, node): @@ -146,7 +160,7 @@ class Inferencer(algorithm.Transformer): self.engine.process(diag) def visit_FunctionDef(self, node): - extractor = LocalExtractor(engine=self.engine) + extractor = LocalExtractor(env_stack=self.env_stack, engine=self.engine) extractor.visit(node) self.env_stack.append(extractor.typing_env) From 76ce364feaab09364a35ebcde65876ffef78907a Mon Sep 17 00:00:00 2001 From: whitequark Date: Tue, 2 Jun 2015 08:53:11 +0300 Subject: [PATCH 006/369] Implement inferencing for AugAssign. --- artiq/py2llvm/typing.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/artiq/py2llvm/typing.py b/artiq/py2llvm/typing.py index 5e270f052..c205ec984 100644 --- a/artiq/py2llvm/typing.py +++ b/artiq/py2llvm/typing.py @@ -216,6 +216,12 @@ class Inferencer(algorithm.Transformer): node.targets[0].loc, node.value.loc) return node + def visit_AugAssign(self, node): + node = self.generic_visit(node) + self._unify(node.target.type, node.value.type, + node.target.loc, node.value.loc) + return node + class Printer(algorithm.Visitor): def __init__(self, buf): self.rewriter = source.Rewriter(buf) From 995d84d4eed083085105647aeadb746e3445cd75 Mon Sep 17 00:00:00 2001 From: whitequark Date: Thu, 4 Jun 2015 14:12:41 +0300 Subject: [PATCH 007/369] Add inferencing for Tuple, List, For. --- artiq/py2llvm/asttyped.py | 69 +++++++++++++++++++++++++++------------ artiq/py2llvm/types.py | 13 +++++++- artiq/py2llvm/typing.py | 27 ++++++++++++++- 3 files changed, 87 insertions(+), 22 deletions(-) diff --git a/artiq/py2llvm/asttyped.py b/artiq/py2llvm/asttyped.py index 21f3b07a1..24d341faa 100644 --- a/artiq/py2llvm/asttyped.py +++ b/artiq/py2llvm/asttyped.py @@ -22,32 +22,61 @@ class scoped(object): list of variables resolved as globals """ -class ClassDefT(ast.ClassDef, scoped): - pass - -class FunctionDefT(ast.FunctionDef, scoped): - pass - -class LambdaT(ast.Lambda, scoped): - pass - -class DictCompT(ast.DictComp, scoped): - pass - -class ListCompT(ast.ListComp, scoped): - pass - -class SetCompT(ast.SetComp, scoped): - pass - class argT(ast.arg, commontyped): pass -class NumT(ast.Num, commontyped): +class ClassDefT(ast.ClassDef, scoped): + pass +class FunctionDefT(ast.FunctionDef, scoped): pass +class AttributeT(ast.Attribute, commontyped): + pass +class BinOpT(ast.BinOp, commontyped): + pass +class BoolOpT(ast.BoolOp, commontyped): + pass +class CallT(ast.Call, commontyped): + pass +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 diff --git a/artiq/py2llvm/types.py b/artiq/py2llvm/types.py index 52cbe7518..ff17c8456 100644 --- a/artiq/py2llvm/types.py +++ b/artiq/py2llvm/types.py @@ -86,6 +86,9 @@ class TMono(Type): def __repr__(self): return "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 \ @@ -150,14 +153,22 @@ def TBool(): """A boolean type.""" return TMono("bool") -def TInt(width=TVar()): +def TInt(width=None): """A generic integer type.""" + if width is None: + width = TVar() return TMono("int", {"width": width}) def TFloat(): """A double-precision floating point type.""" return TMono("float") +def TList(elt=None): + """A generic list type.""" + if elt is None: + elt = TVar() + return TMono("list", {"elt": elt}) + class TypePrinter(object): """ diff --git a/artiq/py2llvm/typing.py b/artiq/py2llvm/typing.py index c205ec984..47c0473bb 100644 --- a/artiq/py2llvm/typing.py +++ b/artiq/py2llvm/typing.py @@ -184,6 +184,8 @@ class Inferencer(algorithm.Transformer): "name '{name}' is not bound to anything", {"name":name}, loc) self.engine.process(diag) + # Visitors that replace node with a typed node + # def visit_arg(self, node): return asttyped.argT(type=self._find_name(node.arg, node.loc), arg=node.arg, annotation=self.visit(node.annotation), @@ -206,6 +208,22 @@ class Inferencer(algorithm.Transformer): return asttyped.NameT(type=self._find_name(node.id, node.loc), id=node.id, ctx=node.ctx, 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=types.TList(), + elts=node.elts, ctx=node.ctx, loc=node.loc) + for elt in node.elts: + self._unify(node.type['elt'], elt.type, + node.loc, elt.loc) + return node + + # Visitors that just unify types + # def visit_Assign(self, node): node = self.generic_visit(node) if len(node.targets) > 1: @@ -222,6 +240,13 @@ class Inferencer(algorithm.Transformer): node.target.loc, node.value.loc) return node + def visit_For(self, node): + node = self.generic_visit(node) + # TODO: support more than just lists + self._unify(TList(node.target.type), node.iter.type, + node.target.loc, node.iter.loc) + return node + class Printer(algorithm.Visitor): def __init__(self, buf): self.rewriter = source.Rewriter(buf) @@ -232,7 +257,7 @@ class Printer(algorithm.Visitor): def generic_visit(self, node): if hasattr(node, 'type'): - self.rewriter.insert_after(node.loc, " : %s" % self.type_printer.name(node.type)) + self.rewriter.insert_after(node.loc, ":%s" % self.type_printer.name(node.type)) super().generic_visit(node) From 10a269d77e9a7ea3eab46e4c9f6dfa27eb845b37 Mon Sep 17 00:00:00 2001 From: whitequark Date: Thu, 4 Jun 2015 14:42:45 +0300 Subject: [PATCH 008/369] Better error message for List inference. --- artiq/py2llvm/typing.py | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/artiq/py2llvm/typing.py b/artiq/py2llvm/typing.py index 47c0473bb..1d89c1729 100644 --- a/artiq/py2llvm/typing.py +++ b/artiq/py2llvm/typing.py @@ -131,18 +131,26 @@ class Inferencer(algorithm.Transformer): self.engine = engine self.env_stack = [{}] - def _unify(self, typea, typeb, loca, locb): + def _unify(self, typea, typeb, loca, locb, kind): try: typea.unify(typeb) except types.UnificationError as e: - note1 = diagnostic.Diagnostic('note', - "expression of type {typea}", - {"typea": types.TypePrinter().name(typea)}, - loca) + if kind == 'generic': + note1 = diagnostic.Diagnostic('note', + "expression of type {typea}", + {"typea": types.TypePrinter().name(typea)}, + loca) + elif kind == 'expects': + note1 = diagnostic.Diagnostic('note', + "expression expecting an operand of type {typea}", + {"typea": types.TypePrinter().name(typea)}, + loca) + note2 = diagnostic.Diagnostic('note', "expression of type {typeb}", {"typeb": types.TypePrinter().name(typeb)}, locb) + if e.typea.find() == typea.find() and e.typeb.find() == typeb.find(): diag = diagnostic.Diagnostic('fatal', "cannot unify {typea} with {typeb}", @@ -198,7 +206,7 @@ class Inferencer(algorithm.Transformer): typ = types.TFloat() else: diag = diagnostic.Diagnostic('fatal', - "numeric type {type} is not supported", node.n.__class__.__name__, + "numeric type {type} is not supported", {"type": node.n.__class__.__name__}, node.loc) self.engine.process(diag) return asttyped.NumT(type=typ, @@ -219,7 +227,7 @@ class Inferencer(algorithm.Transformer): elts=node.elts, ctx=node.ctx, loc=node.loc) for elt in node.elts: self._unify(node.type['elt'], elt.type, - node.loc, elt.loc) + node.loc, elt.loc, kind='expects') return node # Visitors that just unify types @@ -257,7 +265,8 @@ class Printer(algorithm.Visitor): def generic_visit(self, node): if hasattr(node, 'type'): - self.rewriter.insert_after(node.loc, ":%s" % self.type_printer.name(node.type)) + self.rewriter.insert_after(node.loc, + ":%s" % self.type_printer.name(node.type)) super().generic_visit(node) From c9623a106ecd1d1bed5e9ff03450a281cb42ffab Mon Sep 17 00:00:00 2001 From: whitequark Date: Thu, 4 Jun 2015 14:43:00 +0300 Subject: [PATCH 009/369] Error out on unsupported expressions by default. --- artiq/py2llvm/typing.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/artiq/py2llvm/typing.py b/artiq/py2llvm/typing.py index 1d89c1729..857cd4b25 100644 --- a/artiq/py2llvm/typing.py +++ b/artiq/py2llvm/typing.py @@ -255,6 +255,34 @@ class Inferencer(algorithm.Transformer): node.target.loc, node.iter.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) + + visit_Attribute = visit_unsupported + visit_BinOp = visit_unsupported + visit_BoolOp = visit_unsupported + visit_Call = visit_unsupported + visit_Compare = visit_unsupported + visit_Dict = visit_unsupported + visit_DictComp = visit_unsupported + visit_Ellipsis = visit_unsupported + visit_GeneratorExp = visit_unsupported + visit_IfExp = visit_unsupported + visit_Lambda = visit_unsupported + visit_ListComp = visit_unsupported + visit_Set = visit_unsupported + visit_SetComp = visit_unsupported + visit_Str = visit_unsupported + visit_Starred = visit_unsupported + visit_UnaryOp = visit_unsupported + visit_Yield = visit_unsupported + visit_YieldFrom = visit_unsupported + class Printer(algorithm.Visitor): def __init__(self, buf): self.rewriter = source.Rewriter(buf) From 1a08b50f0a1681d37cb75cecd893207790560349 Mon Sep 17 00:00:00 2001 From: whitequark Date: Thu, 4 Jun 2015 14:46:43 +0300 Subject: [PATCH 010/369] Use a single type printer for inference errors. This way, type variable names will be consistent among all printed diagnostics. --- artiq/py2llvm/typing.py | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/artiq/py2llvm/typing.py b/artiq/py2llvm/typing.py index 857cd4b25..4453c8046 100644 --- a/artiq/py2llvm/typing.py +++ b/artiq/py2llvm/typing.py @@ -135,35 +135,34 @@ class Inferencer(algorithm.Transformer): try: typea.unify(typeb) except types.UnificationError as e: + printer = types.TypePrinter() + if kind == 'generic': note1 = diagnostic.Diagnostic('note', "expression of type {typea}", - {"typea": types.TypePrinter().name(typea)}, + {"typea": printer.name(typea)}, loca) elif kind == 'expects': note1 = diagnostic.Diagnostic('note', "expression expecting an operand of type {typea}", - {"typea": types.TypePrinter().name(typea)}, + {"typea": printer.name(typea)}, loca) note2 = diagnostic.Diagnostic('note', "expression of type {typeb}", - {"typeb": types.TypePrinter().name(typeb)}, + {"typeb": printer.name(typeb)}, locb) if e.typea.find() == typea.find() and e.typeb.find() == typeb.find(): diag = diagnostic.Diagnostic('fatal', "cannot unify {typea} with {typeb}", - {"typea": types.TypePrinter().name(typea), - "typeb": types.TypePrinter().name(typeb)}, + {"typea": printer.name(typea), "typeb": printer.name(typeb)}, loca, [locb], notes=[note1, note2]) else: # give more detail diag = diagnostic.Diagnostic('fatal', "cannot unify {typea} with {typeb}: {fraga} is incompatible with {fragb}", - {"typea": types.TypePrinter().name(typea), - "typeb": types.TypePrinter().name(typeb), - "fraga": types.TypePrinter().name(e.typea), - "fragb": types.TypePrinter().name(e.typeb),}, + {"typea": printer.name(typea), "typeb": printer.name(typeb), + "fraga": printer.name(e.typea), "fragb": printer.name(e.typeb)}, loca, [locb], notes=[note1, note2]) self.engine.process(diag) @@ -230,6 +229,16 @@ class Inferencer(algorithm.Transformer): node.loc, elt.loc, kind='expects') return 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, + loc=node.loc) + # TODO: support more than just lists + self._unify(types.TList(node.type), node.value.type, + node.loc, node.value.loc, kind='expects') + return node + # Visitors that just unify types # def visit_Assign(self, node): From 4b01e604dbd606164161747bd9cc076181adcb83 Mon Sep 17 00:00:00 2001 From: whitequark Date: Thu, 4 Jun 2015 14:50:16 +0300 Subject: [PATCH 011/369] Make unification reflective. --- artiq/py2llvm/types.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/artiq/py2llvm/types.py b/artiq/py2llvm/types.py index ff17c8456..dc15c5cb5 100644 --- a/artiq/py2llvm/types.py +++ b/artiq/py2llvm/types.py @@ -80,6 +80,8 @@ class TMono(Type): 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) @@ -110,6 +112,8 @@ class TTuple(Type): 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) @@ -136,7 +140,9 @@ class TValue(Type): return self def unify(self, other): - if self != other: + if isinstance(other, TVar): + other.unify(self) + elif self != other: raise UnificationError(self, other) def __repr__(self): From 6c3b5a95ee4d9c6daeaece3670f545d052cba8ea Mon Sep 17 00:00:00 2001 From: whitequark Date: Thu, 4 Jun 2015 17:53:38 +0300 Subject: [PATCH 012/369] Use proper format function. --- artiq/py2llvm/typing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/artiq/py2llvm/typing.py b/artiq/py2llvm/typing.py index 4453c8046..318f24834 100644 --- a/artiq/py2llvm/typing.py +++ b/artiq/py2llvm/typing.py @@ -303,7 +303,7 @@ class Printer(algorithm.Visitor): def generic_visit(self, node): if hasattr(node, 'type'): self.rewriter.insert_after(node.loc, - ":%s" % self.type_printer.name(node.type)) + ":%s".format(self.type_printer.name(node.type))) super().generic_visit(node) From eb76f594a091b14c528b758035827c7b4705dfa7 Mon Sep 17 00:00:00 2001 From: whitequark Date: Fri, 5 Jun 2015 12:04:27 +0300 Subject: [PATCH 013/369] Replace single-quoted strings with double-quoted. --- artiq/py2llvm/asttyped.py | 2 +- artiq/py2llvm/typing.py | 40 +++++++++++++++++++-------------------- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/artiq/py2llvm/asttyped.py b/artiq/py2llvm/asttyped.py index 24d341faa..848d2625a 100644 --- a/artiq/py2llvm/asttyped.py +++ b/artiq/py2llvm/asttyped.py @@ -9,7 +9,7 @@ from pythonparser.algorithm import Visitor as ASTVisitor class commontyped(ast.commonloc): """A mixin for typed AST nodes.""" - _types = ('type',) + _types = ("type",) def _reprfields(self): return self._fields + self._locs + self._types diff --git a/artiq/py2llvm/typing.py b/artiq/py2llvm/typing.py index 318f24834..9c9bfcc4c 100644 --- a/artiq/py2llvm/typing.py +++ b/artiq/py2llvm/typing.py @@ -89,21 +89,21 @@ class LocalExtractor(algorithm.Visitor): def _check_not_in(self, name, names, curkind, newkind, loc): if name in names: - diag = diagnostic.Diagnostic('fatal', + diag = diagnostic.Diagnostic("fatal", "name '{name}' cannot be {curkind} and {newkind} simultaneously", {"name": name, "curkind": curkind, "newkind": newkind}, loc) self.engine.process(diag) def visit_Global(self, node): for name, loc in zip(node.names, node.name_locs): - self._check_not_in(name, self.nonlocal_, 'nonlocal', 'global', loc) - self._check_not_in(name, self.params, 'a parameter', 'global', loc) + self._check_not_in(name, self.nonlocal_, "nonlocal", "global", loc) + self._check_not_in(name, self.params, "a parameter", "global", loc) self.global_.add(name) def visit_Nonlocal(self, node): for name, loc in zip(node.names, node.name_locs): - self._check_not_in(name, self.global_, 'global', 'nonlocal', loc) - self._check_not_in(name, self.params, 'a parameter', 'nonlocal', loc) + self._check_not_in(name, self.global_, "global", "nonlocal", loc) + self._check_not_in(name, self.params, "a parameter", "nonlocal", loc) found = False for outer_env in reversed(self.env_stack): @@ -111,7 +111,7 @@ class LocalExtractor(algorithm.Visitor): found = True break if not found: - diag = diagnostic.Diagnostic('fatal', + diag = diagnostic.Diagnostic("fatal", "can't declare name '{name}' as nonlocal: it is not bound in any outer scope", {"name": name}, loc, [node.keyword_loc]) @@ -137,29 +137,29 @@ class Inferencer(algorithm.Transformer): except types.UnificationError as e: printer = types.TypePrinter() - if kind == 'generic': - note1 = diagnostic.Diagnostic('note', + if kind == "generic": + note1 = diagnostic.Diagnostic("note", "expression of type {typea}", {"typea": printer.name(typea)}, loca) - elif kind == 'expects': - note1 = diagnostic.Diagnostic('note', + elif kind == "expects": + note1 = diagnostic.Diagnostic("note", "expression expecting an operand of type {typea}", {"typea": printer.name(typea)}, loca) - note2 = diagnostic.Diagnostic('note', + note2 = diagnostic.Diagnostic("note", "expression of type {typeb}", {"typeb": printer.name(typeb)}, locb) if e.typea.find() == typea.find() and e.typeb.find() == typeb.find(): - diag = diagnostic.Diagnostic('fatal', + diag = diagnostic.Diagnostic("fatal", "cannot unify {typea} with {typeb}", {"typea": printer.name(typea), "typeb": printer.name(typeb)}, loca, [locb], notes=[note1, note2]) else: # give more detail - diag = diagnostic.Diagnostic('fatal', + diag = diagnostic.Diagnostic("fatal", "cannot unify {typea} with {typeb}: {fraga} is incompatible with {fragb}", {"typea": printer.name(typea), "typeb": printer.name(typeb), "fraga": printer.name(e.typea), "fragb": printer.name(e.typeb)}, @@ -187,7 +187,7 @@ class Inferencer(algorithm.Transformer): for typing_env in reversed(self.env_stack): if name in typing_env: return typing_env[name] - diag = diagnostic.Diagnostic('fatal', + diag = diagnostic.Diagnostic("fatal", "name '{name}' is not bound to anything", {"name":name}, loc) self.engine.process(diag) @@ -204,7 +204,7 @@ class Inferencer(algorithm.Transformer): elif isinstance(node.n, float): typ = types.TFloat() else: - diag = diagnostic.Diagnostic('fatal', + diag = diagnostic.Diagnostic("fatal", "numeric type {type} is not supported", {"type": node.n.__class__.__name__}, node.loc) self.engine.process(diag) @@ -225,8 +225,8 @@ class Inferencer(algorithm.Transformer): node = asttyped.ListT(type=types.TList(), elts=node.elts, ctx=node.ctx, loc=node.loc) for elt in node.elts: - self._unify(node.type['elt'], elt.type, - node.loc, elt.loc, kind='expects') + self._unify(node.type["elt"], elt.type, + node.loc, elt.loc, kind="expects") return node def visit_Subscript(self, node): @@ -236,7 +236,7 @@ class Inferencer(algorithm.Transformer): loc=node.loc) # TODO: support more than just lists self._unify(types.TList(node.type), node.value.type, - node.loc, node.value.loc, kind='expects') + node.loc, node.value.loc, kind="expects") return node # Visitors that just unify types @@ -267,7 +267,7 @@ class Inferencer(algorithm.Transformer): # Unsupported visitors # def visit_unsupported(self, node): - diag = diagnostic.Diagnostic('fatal', + diag = diagnostic.Diagnostic("fatal", "this syntax is not supported", {}, node.loc) self.engine.process(diag) @@ -301,7 +301,7 @@ class Printer(algorithm.Visitor): return self.rewriter.rewrite() def generic_visit(self, node): - if hasattr(node, 'type'): + if hasattr(node, "type"): self.rewriter.insert_after(node.loc, ":%s".format(self.type_printer.name(node.type))) From d08598fa0f075e2f997245e4add8aeb036723ed8 Mon Sep 17 00:00:00 2001 From: whitequark Date: Sat, 6 Jun 2015 15:12:40 +0300 Subject: [PATCH 014/369] Add support for NameConstant. --- artiq/py2llvm/types.py | 11 +++++++++-- artiq/py2llvm/typing.py | 9 ++++++++- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/artiq/py2llvm/types.py b/artiq/py2llvm/types.py index dc15c5cb5..078051883 100644 --- a/artiq/py2llvm/types.py +++ b/artiq/py2llvm/types.py @@ -155,6 +155,10 @@ class TValue(Type): def __ne__(self, other): return not (self == other) +def TNone(): + """The type of None.""" + return TMono("NoneType") + def TBool(): """A boolean type.""" return TMono("bool") @@ -193,8 +197,11 @@ class TypePrinter(object): self.map[typ] = "'%s" % next(self.gen) return self.map[typ] elif isinstance(typ, TMono): - return "%s(%s)" % (typ.name, ", ".join( - ["%s=%s" % (k, self.name(typ.params[k])) for k in typ.params])) + if typ.params == {}: + return typ.name + else: + return "%s(%s)" % (typ.name, ", ".join( + ["%s=%s" % (k, self.name(typ.params[k])) for k in typ.params])) elif isinstance(typ, TTuple): if len(typ.elts) == 1: return "(%s,)" % self.name(typ.elts[0]) diff --git a/artiq/py2llvm/typing.py b/artiq/py2llvm/typing.py index 9c9bfcc4c..da793f6d3 100644 --- a/artiq/py2llvm/typing.py +++ b/artiq/py2llvm/typing.py @@ -215,6 +215,13 @@ class Inferencer(algorithm.Transformer): 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 = types.TBool() + elif node.value is None: + typ = types.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]), @@ -303,7 +310,7 @@ class Printer(algorithm.Visitor): def generic_visit(self, node): if hasattr(node, "type"): self.rewriter.insert_after(node.loc, - ":%s".format(self.type_printer.name(node.type))) + ":{}".format(self.type_printer.name(node.type))) super().generic_visit(node) From 5f06c6af10358e7b929b5eb67a50d959c2d74bb4 Mon Sep 17 00:00:00 2001 From: whitequark Date: Sat, 6 Jun 2015 15:12:56 +0300 Subject: [PATCH 015/369] Add support for Return. --- artiq/py2llvm/typing.py | 88 ++++++++++++++++++++++++++++------------- 1 file changed, 61 insertions(+), 27 deletions(-) diff --git a/artiq/py2llvm/typing.py b/artiq/py2llvm/typing.py index da793f6d3..e93af3225 100644 --- a/artiq/py2llvm/typing.py +++ b/artiq/py2llvm/typing.py @@ -130,6 +130,7 @@ class Inferencer(algorithm.Transformer): def __init__(self, engine): self.engine = engine self.env_stack = [{}] + self.function = None # currently visited function def _unify(self, typea, typeb, loca, locb, kind): try: @@ -137,21 +138,32 @@ class Inferencer(algorithm.Transformer): except types.UnificationError as e: printer = types.TypePrinter() - if kind == "generic": - note1 = diagnostic.Diagnostic("note", - "expression of type {typea}", - {"typea": printer.name(typea)}, - loca) - elif kind == "expects": + if kind == "expects": note1 = diagnostic.Diagnostic("note", "expression expecting an operand of type {typea}", {"typea": printer.name(typea)}, loca) + elif kind == "return_type" or kind == "return_type_none": + note1 = diagnostic.Diagnostic("note", + "function with return type {typea}", + {"typea": printer.name(typea)}, + loca) + else: + note1 = diagnostic.Diagnostic("note", + "expression of type {typea}", + {"typea": printer.name(typea)}, + loca) - note2 = diagnostic.Diagnostic("note", - "expression of type {typeb}", - {"typeb": printer.name(typeb)}, - locb) + if kind == "return_type_none": + note2 = diagnostic.Diagnostic("note", + "implied expression of type {typeb}", + {"typeb": printer.name(typeb)}, + locb) + else: + note2 = diagnostic.Diagnostic("note", + "expression of type {typeb}", + {"typeb": printer.name(typeb)}, + locb) if e.typea.find() == typea.find() and e.typeb.find() == typeb.find(): diag = diagnostic.Diagnostic("fatal", @@ -166,23 +178,6 @@ class Inferencer(algorithm.Transformer): loca, [locb], notes=[note1, note2]) self.engine.process(diag) - def visit_FunctionDef(self, node): - extractor = LocalExtractor(env_stack=self.env_stack, engine=self.engine) - extractor.visit(node) - - self.env_stack.append(extractor.typing_env) - node = asttyped.FunctionDefT( - typing_env=extractor.typing_env, globals_in_scope=extractor.global_, - name=node.name, args=self.visit(node.args), returns=self.visit(node.returns), - body=[self.visit(x) for x in 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) - self.generic_visit(node) - self.env_stack.pop() - - return node - def _find_name(self, name, loc): for typing_env in reversed(self.env_stack): if name in typing_env: @@ -198,6 +193,39 @@ class Inferencer(algorithm.Transformer): arg=node.arg, annotation=self.visit(node.annotation), arg_loc=node.arg_loc, colon_loc=node.colon_loc, loc=node.loc) + def visit_FunctionDef(self, node): + extractor = LocalExtractor(env_stack=self.env_stack, engine=self.engine) + extractor.visit(node) + + self.env_stack.append(extractor.typing_env) + + node = asttyped.FunctionDefT( + typing_env=extractor.typing_env, globals_in_scope=extractor.global_, + 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) + + old_function, self.function = self.function, node + self.generic_visit(node) + self.function = old_function + + self.env_stack.pop() + + return node + + def visit_Return(self, node): + node = self.generic_visit(node) + if node.value is None: + self._unify(self.function.return_type, types.TNone(), + self.function.name_loc, node.value.loc, kind="return_type_none") + else: + self._unify(self.function.return_type, node.value.type, + self.function.name_loc, node.value.loc, kind="return_type") + def visit_Num(self, node): if isinstance(node.n, int): typ = types.TInt() @@ -307,6 +335,12 @@ class Printer(algorithm.Visitor): def rewrite(self): return self.rewriter.rewrite() + def visit_FunctionDefT(self, node): + self.rewriter.insert_before(node.colon_loc, + "->{}".format(self.type_printer.name(node.return_type))) + + super().generic_visit(node) + def generic_visit(self, node): if hasattr(node, "type"): self.rewriter.insert_after(node.loc, From 7f77632f1a15294c6ad4522b5939d71f242cea71 Mon Sep 17 00:00:00 2001 From: whitequark Date: Sat, 6 Jun 2015 16:58:23 +0300 Subject: [PATCH 016/369] Add lit-based tests for type inferencer. --- .gitignore | 1 + artiq/py2llvm/typing.py | 29 ++++++++++++++++++++++------- lit-test/harness.py | 23 +++++++++++++++++++++++ lit-test/lit.cfg | 12 ++++++++++++ lit-test/py2llvm/typing/unify.py | 26 ++++++++++++++++++++++++++ setup.py | 2 +- 6 files changed, 85 insertions(+), 8 deletions(-) create mode 100644 lit-test/harness.py create mode 100644 lit-test/lit.cfg create mode 100644 lit-test/py2llvm/typing/unify.py diff --git a/.gitignore b/.gitignore index a8b35b740..22d473a3f 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,4 @@ doc/manual/_build /*.egg-info /.coverage examples/master/results +Output/ diff --git a/artiq/py2llvm/typing.py b/artiq/py2llvm/typing.py index e93af3225..310819530 100644 --- a/artiq/py2llvm/typing.py +++ b/artiq/py2llvm/typing.py @@ -129,10 +129,10 @@ class LocalExtractor(algorithm.Visitor): class Inferencer(algorithm.Transformer): def __init__(self, engine): self.engine = engine - self.env_stack = [{}] + self.env_stack = [] self.function = None # currently visited function - def _unify(self, typea, typeb, loca, locb, kind): + def _unify(self, typea, typeb, loca, locb, kind='generic'): try: typea.unify(typeb) except types.UnificationError as e: @@ -186,6 +186,13 @@ class Inferencer(algorithm.Transformer): "name '{name}' is not bound to anything", {"name":name}, loc) self.engine.process(diag) + def visit_root(self, node): + extractor = LocalExtractor(env_stack=self.env_stack, engine=self.engine) + extractor.visit(node) + self.env_stack.append(extractor.typing_env) + + return self.visit(node) + # Visitors that replace node with a typed node # def visit_arg(self, node): @@ -349,17 +356,25 @@ class Printer(algorithm.Visitor): super().generic_visit(node) def main(): - import sys, fileinput + import sys, fileinput, os + + inference_mode = True + engine = diagnostic.Engine(all_errors_are_fatal=True) try: - buf = source.Buffer("".join(fileinput.input()), fileinput.filename()) - parsed = parse_buffer(buf, engine=engine) - typed = Inferencer(engine=engine).visit(parsed) + buf = source.Buffer("".join(fileinput.input()), os.path.basename(fileinput.filename())) + parsed, comments = parse_buffer(buf, engine=engine) + typed = Inferencer(engine=engine).visit_root(parsed) 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) except diagnostic.Error as e: - print("\n".join(e.diagnostic.render()), file=sys.stderr) + if inference_mode: + print("\n".join(e.diagnostic.render()), file=sys.stderr) + exit(1) if __name__ == "__main__": main() diff --git a/lit-test/harness.py b/lit-test/harness.py new file mode 100644 index 000000000..1779e3266 --- /dev/null +++ b/lit-test/harness.py @@ -0,0 +1,23 @@ +""" +The purpose of this harness is to emulate the behavior of +the python executable, but add the ARTIQ root to sys.path +beforehand. + +This is necessary because eggs override the PYTHONPATH environment +variable, but not current directory; therefore `python -m artiq...` +ran from the ARTIQ root would work, but there is no simple way to +emulate the same behavior when invoked under lit. +""" + +import sys, os, argparse, importlib + +parser = argparse.ArgumentParser(description=__doc__) +parser.add_argument('-m', metavar='mod', type=str, help='run library module as a script') +parser.add_argument('args', type=str, nargs='+', help='arguments passed to program in sys.argv[1:]') +args = parser.parse_args(sys.argv[1:]) + +artiq_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..')) +sys.path.insert(1, artiq_path) + +sys.argv[1:] = args.args +importlib.import_module(args.m).main() diff --git a/lit-test/lit.cfg b/lit-test/lit.cfg new file mode 100644 index 000000000..398164291 --- /dev/null +++ b/lit-test/lit.cfg @@ -0,0 +1,12 @@ +import lit.util +import lit.formats + +config.name = 'ARTIQ' +config.test_format = lit.formats.ShTest() +config.suffixes = ['.py'] +config.excludes = ['harness.py'] +config.test_source_root = os.path.dirname(__file__) + +python_executable = 'python3' +harness = '{} {}'.format(python_executable, os.path.join(config.test_source_root, 'harness.py')) +config.substitutions.append( ('%python', harness) ) diff --git a/lit-test/py2llvm/typing/unify.py b/lit-test/py2llvm/typing/unify.py new file mode 100644 index 000000000..879c55d0f --- /dev/null +++ b/lit-test/py2llvm/typing/unify.py @@ -0,0 +1,26 @@ +# RUN: %python -m artiq.py2llvm.typing %s >%t +# RUN: OutputCheck %s --file-to-check=%t + +a = 1 +# CHECK-L: a:int(width='a) + +b = a +# CHECK-L: b:int(width='a) + +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='b) + +h = [1] +# CHECK-L: h:list(elt=int(width='c)) diff --git a/setup.py b/setup.py index 44087dc4c..427b9e9aa 100755 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ from setuptools import setup, find_packages requirements = [ "sphinx", "sphinx-argparse", "pyserial", "numpy", "scipy", "python-dateutil", "prettytable", "h5py", "pydaqmx", "pyelftools", - "quamash", "pyqtgraph", "pythonparser" + "quamash", "pyqtgraph", "pythonparser", "lit", "OutputCheck" ] scripts = [ From 98fe1521598204d6c7476c3233bdb63b40793ca6 Mon Sep 17 00:00:00 2001 From: whitequark Date: Sat, 6 Jun 2015 17:15:06 +0300 Subject: [PATCH 017/369] Add lit-based tests for diagnostics. --- artiq/py2llvm/typing.py | 8 +++++++- lit-test/harness.py | 6 ++++-- lit-test/py2llvm/typing/error_unify.py | 8 ++++++++ 3 files changed, 19 insertions(+), 3 deletions(-) create mode 100644 lit-test/py2llvm/typing/error_unify.py diff --git a/artiq/py2llvm/typing.py b/artiq/py2llvm/typing.py index 310819530..aef05b9fd 100644 --- a/artiq/py2llvm/typing.py +++ b/artiq/py2llvm/typing.py @@ -358,7 +358,11 @@ class Printer(algorithm.Visitor): def main(): import sys, fileinput, os - inference_mode = True + if sys.argv[1] == '+diag': + del sys.argv[1] + inference_mode = False + else: + inference_mode = True engine = diagnostic.Engine(all_errors_are_fatal=True) try: @@ -375,6 +379,8 @@ def main(): if inference_mode: print("\n".join(e.diagnostic.render()), file=sys.stderr) exit(1) + else: + print("\n".join(e.diagnostic.render(only_line=True))) if __name__ == "__main__": main() diff --git a/lit-test/harness.py b/lit-test/harness.py index 1779e3266..9821ae61f 100644 --- a/lit-test/harness.py +++ b/lit-test/harness.py @@ -12,8 +12,10 @@ emulate the same behavior when invoked under lit. import sys, os, argparse, importlib parser = argparse.ArgumentParser(description=__doc__) -parser.add_argument('-m', metavar='mod', type=str, help='run library module as a script') -parser.add_argument('args', type=str, nargs='+', help='arguments passed to program in sys.argv[1:]') +parser.add_argument('-m', metavar='mod', type=str, required=True, + help='run library module as a script') +parser.add_argument('args', type=str, nargs='+', + help='arguments passed to program in sys.argv[1:]') args = parser.parse_args(sys.argv[1:]) artiq_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..')) diff --git a/lit-test/py2llvm/typing/error_unify.py b/lit-test/py2llvm/typing/error_unify.py new file mode 100644 index 000000000..859e49205 --- /dev/null +++ b/lit-test/py2llvm/typing/error_unify.py @@ -0,0 +1,8 @@ +# RUN: %python -m artiq.py2llvm.typing +diag %s >%t +# RUN: OutputCheck %s --file-to-check=%t + +a = 1 +b = [] + +# CHECK-L: ${LINE:+1}: fatal: cannot unify int(width='a) with list(elt='b) +a = b From a8ff9d01527770b25bd487238c2eea70c5f5c457 Mon Sep 17 00:00:00 2001 From: whitequark Date: Sat, 6 Jun 2015 17:55:04 +0300 Subject: [PATCH 018/369] AugAssign does not create a binding. --- artiq/py2llvm/typing.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/artiq/py2llvm/typing.py b/artiq/py2llvm/typing.py index aef05b9fd..20ee758ad 100644 --- a/artiq/py2llvm/typing.py +++ b/artiq/py2llvm/typing.py @@ -35,11 +35,6 @@ class LocalExtractor(algorithm.Visitor): self.visit_in_assign(target) self.visit(node.value) - def visit_AugAssign(self, node): - self.visit_in_assign(node.target) - self.visit(node.op) - self.visit(node.value) - def visit_For(self, node): self.visit_in_assign(node.target) self.visit(node.iter) From 159692339d5fbebde8aade671b3e200dfe8cad83 Mon Sep 17 00:00:00 2001 From: whitequark Date: Sat, 6 Jun 2015 18:05:06 +0300 Subject: [PATCH 019/369] Add tests for all diagnostics and unifications. --- artiq/py2llvm/typing.py | 57 +++++++++++-------- .../py2llvm/typing/error_local_unbound.py | 5 ++ .../py2llvm/typing/error_nonlocal_global.py | 25 ++++++++ lit-test/py2llvm/typing/unify.py | 8 +++ 4 files changed, 70 insertions(+), 25 deletions(-) create mode 100644 lit-test/py2llvm/typing/error_local_unbound.py create mode 100644 lit-test/py2llvm/typing/error_nonlocal_global.py diff --git a/artiq/py2llvm/typing.py b/artiq/py2llvm/typing.py index 20ee758ad..7624a562b 100644 --- a/artiq/py2llvm/typing.py +++ b/artiq/py2llvm/typing.py @@ -84,21 +84,25 @@ class LocalExtractor(algorithm.Visitor): def _check_not_in(self, name, names, curkind, newkind, loc): if name in names: - diag = diagnostic.Diagnostic("fatal", + 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): - self._check_not_in(name, self.nonlocal_, "nonlocal", "global", loc) - self._check_not_in(name, self.params, "a parameter", "global", loc) + 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) def visit_Nonlocal(self, node): for name, loc in zip(node.names, node.name_locs): - self._check_not_in(name, self.global_, "global", "nonlocal", loc) - self._check_not_in(name, self.params, "a parameter", "nonlocal", loc) + if self._check_not_in(name, self.global_, "global", "nonlocal", loc) or \ + self._check_not_in(name, self.params, "a parameter", "nonlocal", loc): + continue found = False for outer_env in reversed(self.env_stack): @@ -106,11 +110,12 @@ class LocalExtractor(algorithm.Visitor): found = True break if not found: - diag = diagnostic.Diagnostic("fatal", + diag = diagnostic.Diagnostic("error", "can't 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) @@ -355,27 +360,29 @@ def main(): if sys.argv[1] == '+diag': del sys.argv[1] - inference_mode = False + def process_diagnostic(diag): + print("\n".join(diag.render(only_line=True))) + if diag.level == 'fatal': + exit() else: - inference_mode = True + def process_diagnostic(diag): + print("\n".join(diag.render())) + if diag.level == 'fatal': + exit(1) + + engine = diagnostic.Engine() + engine.process = process_diagnostic + + buf = source.Buffer("".join(fileinput.input()), os.path.basename(fileinput.filename())) + parsed, comments = parse_buffer(buf, engine=engine) + typed = Inferencer(engine=engine).visit_root(parsed) + 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) - engine = diagnostic.Engine(all_errors_are_fatal=True) - try: - buf = source.Buffer("".join(fileinput.input()), os.path.basename(fileinput.filename())) - parsed, comments = parse_buffer(buf, engine=engine) - typed = Inferencer(engine=engine).visit_root(parsed) - 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) - except diagnostic.Error as e: - if inference_mode: - print("\n".join(e.diagnostic.render()), file=sys.stderr) - exit(1) - else: - print("\n".join(e.diagnostic.render(only_line=True))) if __name__ == "__main__": main() diff --git a/lit-test/py2llvm/typing/error_local_unbound.py b/lit-test/py2llvm/typing/error_local_unbound.py new file mode 100644 index 000000000..ceb61da83 --- /dev/null +++ b/lit-test/py2llvm/typing/error_local_unbound.py @@ -0,0 +1,5 @@ +# RUN: %python -m artiq.py2llvm.typing +diag %s >%t +# RUN: OutputCheck %s --file-to-check=%t + +# CHECK-L: ${LINE:+1}: fatal: name 'x' is not bound to anything +x diff --git a/lit-test/py2llvm/typing/error_nonlocal_global.py b/lit-test/py2llvm/typing/error_nonlocal_global.py new file mode 100644 index 000000000..8586d418d --- /dev/null +++ b/lit-test/py2llvm/typing/error_nonlocal_global.py @@ -0,0 +1,25 @@ +# RUN: %python -m artiq.py2llvm.typing +diag %s >%t +# RUN: OutputCheck %s --file-to-check=%t + +def a(): + # CHECK-L: ${LINE:+1}: error: can't declare name 'x' as nonlocal: it is not bound in any outer scope + nonlocal x + +x = 1 +def b(): + nonlocal x + # CHECK-L: ${LINE:+1}: error: name 'x' cannot be nonlocal and global simultaneously + global x + +def c(): + global x + # CHECK-L: ${LINE:+1}: error: name 'x' cannot be global and nonlocal simultaneously + nonlocal x + +def d(x): + # CHECK-L: ${LINE:+1}: error: name 'x' cannot be a parameter and global simultaneously + global x + +def d(x): + # CHECK-L: ${LINE:+1}: error: name 'x' cannot be a parameter and nonlocal simultaneously + nonlocal x diff --git a/lit-test/py2llvm/typing/unify.py b/lit-test/py2llvm/typing/unify.py index 879c55d0f..60f9dba11 100644 --- a/lit-test/py2llvm/typing/unify.py +++ b/lit-test/py2llvm/typing/unify.py @@ -24,3 +24,11 @@ g = [] h = [1] # CHECK-L: h:list(elt=int(width='c)) + +i = [] +i[0] = 1 +# CHECK-L: i:list(elt=int(width='d)) + +j = [] +j += [1.0] +# CHECK-L: j:list(elt=float) From 9953302cb64dc56f618568bd327e4c397517f747 Mon Sep 17 00:00:00 2001 From: whitequark Date: Thu, 11 Jun 2015 02:58:29 +0300 Subject: [PATCH 020/369] Move old py2llvm code to artiq/py2llvm_old. --- artiq/py2llvm/__init__.py | 6 ------ artiq/py2llvm_old/__init__.py | 6 ++++++ artiq/{py2llvm => py2llvm_old}/ast_body.py | 0 artiq/{py2llvm => py2llvm_old}/base_types.py | 0 artiq/{py2llvm => py2llvm_old}/fractions.py | 0 artiq/{py2llvm => py2llvm_old}/infer_types.py | 0 artiq/{py2llvm => py2llvm_old}/iterators.py | 0 artiq/{py2llvm => py2llvm_old}/lists.py | 0 artiq/{py2llvm => py2llvm_old}/module.py | 0 artiq/{py2llvm => py2llvm_old}/tools.py | 0 artiq/{py2llvm => py2llvm_old}/values.py | 0 11 files changed, 6 insertions(+), 6 deletions(-) create mode 100644 artiq/py2llvm_old/__init__.py rename artiq/{py2llvm => py2llvm_old}/ast_body.py (100%) rename artiq/{py2llvm => py2llvm_old}/base_types.py (100%) rename artiq/{py2llvm => py2llvm_old}/fractions.py (100%) rename artiq/{py2llvm => py2llvm_old}/infer_types.py (100%) rename artiq/{py2llvm => py2llvm_old}/iterators.py (100%) rename artiq/{py2llvm => py2llvm_old}/lists.py (100%) rename artiq/{py2llvm => py2llvm_old}/module.py (100%) rename artiq/{py2llvm => py2llvm_old}/tools.py (100%) rename artiq/{py2llvm => py2llvm_old}/values.py (100%) diff --git a/artiq/py2llvm/__init__.py b/artiq/py2llvm/__init__.py index fefb2b9ff..e69de29bb 100644 --- a/artiq/py2llvm/__init__.py +++ b/artiq/py2llvm/__init__.py @@ -1,6 +0,0 @@ -from artiq.py2llvm.module import Module - -def get_runtime_binary(env, func_def): - module = Module(env) - module.compile_function(func_def, dict()) - return module.emit_object() diff --git a/artiq/py2llvm_old/__init__.py b/artiq/py2llvm_old/__init__.py new file mode 100644 index 000000000..fefb2b9ff --- /dev/null +++ b/artiq/py2llvm_old/__init__.py @@ -0,0 +1,6 @@ +from artiq.py2llvm.module import Module + +def get_runtime_binary(env, func_def): + module = Module(env) + module.compile_function(func_def, dict()) + return module.emit_object() diff --git a/artiq/py2llvm/ast_body.py b/artiq/py2llvm_old/ast_body.py similarity index 100% rename from artiq/py2llvm/ast_body.py rename to artiq/py2llvm_old/ast_body.py diff --git a/artiq/py2llvm/base_types.py b/artiq/py2llvm_old/base_types.py similarity index 100% rename from artiq/py2llvm/base_types.py rename to artiq/py2llvm_old/base_types.py diff --git a/artiq/py2llvm/fractions.py b/artiq/py2llvm_old/fractions.py similarity index 100% rename from artiq/py2llvm/fractions.py rename to artiq/py2llvm_old/fractions.py diff --git a/artiq/py2llvm/infer_types.py b/artiq/py2llvm_old/infer_types.py similarity index 100% rename from artiq/py2llvm/infer_types.py rename to artiq/py2llvm_old/infer_types.py diff --git a/artiq/py2llvm/iterators.py b/artiq/py2llvm_old/iterators.py similarity index 100% rename from artiq/py2llvm/iterators.py rename to artiq/py2llvm_old/iterators.py diff --git a/artiq/py2llvm/lists.py b/artiq/py2llvm_old/lists.py similarity index 100% rename from artiq/py2llvm/lists.py rename to artiq/py2llvm_old/lists.py diff --git a/artiq/py2llvm/module.py b/artiq/py2llvm_old/module.py similarity index 100% rename from artiq/py2llvm/module.py rename to artiq/py2llvm_old/module.py diff --git a/artiq/py2llvm/tools.py b/artiq/py2llvm_old/tools.py similarity index 100% rename from artiq/py2llvm/tools.py rename to artiq/py2llvm_old/tools.py diff --git a/artiq/py2llvm/values.py b/artiq/py2llvm_old/values.py similarity index 100% rename from artiq/py2llvm/values.py rename to artiq/py2llvm_old/values.py From ba9a7d087dd424c55dec93f64ef79f4c99cbdfc2 Mon Sep 17 00:00:00 2001 From: whitequark Date: Thu, 11 Jun 2015 03:22:20 +0300 Subject: [PATCH 021/369] Add support for IfExp. --- artiq/py2llvm/typing.py | 36 +++++++++++++++++++++++++------- lit-test/py2llvm/typing/unify.py | 6 ++++++ 2 files changed, 35 insertions(+), 7 deletions(-) diff --git a/artiq/py2llvm/typing.py b/artiq/py2llvm/typing.py index 7624a562b..7e8a5fad9 100644 --- a/artiq/py2llvm/typing.py +++ b/artiq/py2llvm/typing.py @@ -165,17 +165,18 @@ class Inferencer(algorithm.Transformer): {"typeb": printer.name(typeb)}, locb) + highlights = [locb] if locb else [] if e.typea.find() == typea.find() and e.typeb.find() == typeb.find(): diag = diagnostic.Diagnostic("fatal", "cannot unify {typea} with {typeb}", {"typea": printer.name(typea), "typeb": printer.name(typeb)}, - loca, [locb], notes=[note1, note2]) + loca, highlights, notes=[note1, note2]) else: # give more detail diag = diagnostic.Diagnostic("fatal", "cannot unify {typea} with {typeb}: {fraga} is incompatible with {fragb}", {"typea": printer.name(typea), "typeb": printer.name(typeb), "fraga": printer.name(e.typea), "fragb": printer.name(e.typeb)}, - loca, [locb], notes=[note1, note2]) + loca, highlights, notes=[note1, note2]) self.engine.process(diag) def _find_name(self, name, loc): @@ -281,6 +282,29 @@ class Inferencer(algorithm.Transformer): node.loc, node.value.loc, kind="expects") return node + def visit_IfExp(self, node): + node = self.generic_visit(node) + self._unify(node.body.type, node.orelse.type, + node.body.loc, node.orelse.loc) + return asttyped.IfExpT(type=node.body.type, + test=node.test, body=node.body, orelse=node.orelse, + if_loc=node.if_loc, else_loc=node.else_loc, loc=node.loc) + + def visit_BoolOp(self, node): + node = self.generic_visit(node) + for value, op_loc in zip(node.values, node.op_locs): + def makenotes(printer, typea, typeb, loca, locb): + return [ + diagnostic.Diagnostic("note", + "py2llvm requires boolean operations to have boolean operands", {}, + op_loc) + ] + self._unify(value.type, types.TBool(), + value.loc, None, makenotes) + return asttyped.BoolOpT(type=types.TBool(), + op=node.op, values=node.values, + op_locs=node.op_locs, loc=node.loc) + # Visitors that just unify types # def visit_Assign(self, node): @@ -316,14 +340,12 @@ class Inferencer(algorithm.Transformer): visit_Attribute = visit_unsupported visit_BinOp = visit_unsupported - visit_BoolOp = visit_unsupported visit_Call = visit_unsupported visit_Compare = visit_unsupported visit_Dict = visit_unsupported visit_DictComp = visit_unsupported visit_Ellipsis = visit_unsupported visit_GeneratorExp = visit_unsupported - visit_IfExp = visit_unsupported visit_Lambda = visit_unsupported visit_ListComp = visit_unsupported visit_Set = visit_unsupported @@ -343,18 +365,18 @@ class Printer(algorithm.Visitor): 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 generic_visit(self, node): super().generic_visit(node) - def generic_visit(self, node): if hasattr(node, "type"): self.rewriter.insert_after(node.loc, ":{}".format(self.type_printer.name(node.type))) - super().generic_visit(node) - def main(): import sys, fileinput, os diff --git a/lit-test/py2llvm/typing/unify.py b/lit-test/py2llvm/typing/unify.py index 60f9dba11..2093cc3d0 100644 --- a/lit-test/py2llvm/typing/unify.py +++ b/lit-test/py2llvm/typing/unify.py @@ -32,3 +32,9 @@ i[0] = 1 j = [] j += [1.0] # CHECK-L: j:list(elt=float) + +1 if a else 2 +# CHECK-L: 1:int(width='f) if a:int(width='a) else 2:int(width='f):int(width='f) + +True and False +# CHECK-L: True:bool and False:bool:bool From b8ce3f85bd68adcfad95ac76cfa5875b084eaf7d Mon Sep 17 00:00:00 2001 From: whitequark Date: Thu, 11 Jun 2015 03:55:06 +0300 Subject: [PATCH 022/369] Refactor error reporting in _unify to factor out custom notes. --- artiq/py2llvm/typing.py | 81 ++++++++++--------- .../py2llvm/typing/error_nonlocal_global.py | 2 +- lit-test/py2llvm/typing/error_return.py | 16 ++++ lit-test/py2llvm/typing/error_unify.py | 7 +- 4 files changed, 68 insertions(+), 38 deletions(-) create mode 100644 lit-test/py2llvm/typing/error_return.py diff --git a/artiq/py2llvm/typing.py b/artiq/py2llvm/typing.py index 7e8a5fad9..1cfb50c20 100644 --- a/artiq/py2llvm/typing.py +++ b/artiq/py2llvm/typing.py @@ -111,7 +111,7 @@ class LocalExtractor(algorithm.Visitor): break if not found: diag = diagnostic.Diagnostic("error", - "can't declare name '{name}' as nonlocal: it is not bound in any outer scope", + "cannot declare name '{name}' as nonlocal: it is not bound in any outer scope", {"name": name}, loc, [node.keyword_loc]) self.engine.process(diag) @@ -132,51 +132,38 @@ class Inferencer(algorithm.Transformer): self.env_stack = [] self.function = None # currently visited function - def _unify(self, typea, typeb, loca, locb, kind='generic'): + def _unify(self, typea, typeb, loca, locb, makenotes=None): try: typea.unify(typeb) except types.UnificationError as e: printer = types.TypePrinter() - if kind == "expects": - note1 = diagnostic.Diagnostic("note", - "expression expecting an operand of type {typea}", - {"typea": printer.name(typea)}, - loca) - elif kind == "return_type" or kind == "return_type_none": - note1 = diagnostic.Diagnostic("note", - "function with return type {typea}", - {"typea": printer.name(typea)}, - loca) + if makenotes: + notes = makenotes(printer, typea, typeb, loca, locb) else: - note1 = diagnostic.Diagnostic("note", - "expression of type {typea}", - {"typea": printer.name(typea)}, - loca) - - if kind == "return_type_none": - note2 = diagnostic.Diagnostic("note", - "implied expression of type {typeb}", - {"typeb": printer.name(typeb)}, - locb) - else: - note2 = diagnostic.Diagnostic("note", - "expression of type {typeb}", - {"typeb": printer.name(typeb)}, - locb) + notes = [ + diagnostic.Diagnostic("note", + "expression of type {typea}", + {"typea": printer.name(typea)}, + loca), + 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(): - diag = diagnostic.Diagnostic("fatal", + diag = diagnostic.Diagnostic("error", "cannot unify {typea} with {typeb}", {"typea": printer.name(typea), "typeb": printer.name(typeb)}, - loca, highlights, notes=[note1, note2]) + loca, highlights, notes) else: # give more detail - diag = diagnostic.Diagnostic("fatal", + diag = diagnostic.Diagnostic("error", "cannot unify {typea} with {typeb}: {fraga} is incompatible with {fragb}", {"typea": printer.name(typea), "typeb": printer.name(typeb), "fraga": printer.name(e.typea), "fragb": printer.name(e.typeb)}, - loca, highlights, notes=[note1, note2]) + loca, highlights, notes) self.engine.process(diag) def _find_name(self, name, loc): @@ -227,12 +214,23 @@ class Inferencer(algorithm.Transformer): def visit_Return(self, node): node = 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, types.TNone(), - self.function.name_loc, node.value.loc, kind="return_type_none") + self.function.name_loc, node.loc, makenotes) else: self._unify(self.function.return_type, node.value.type, - self.function.name_loc, node.value.loc, kind="return_type") + self.function.name_loc, node.value.loc, makenotes) def visit_Num(self, node): if isinstance(node.n, int): @@ -267,9 +265,20 @@ class Inferencer(algorithm.Transformer): node = self.generic_visit(node) node = asttyped.ListT(type=types.TList(), elts=node.elts, ctx=node.ctx, loc=node.loc) + def makenotes(printer, typea, typeb, loca, locb): + return [ + diagnostic.Diagnostic("note", + "a list of type {typea}", + {"typea": printer.name(node.type)}, + loca), + diagnostic.Diagnostic("note", + "a list element of type {typeb}", + {"typeb": printer.name(typeb)}, + locb) + ] for elt in node.elts: self._unify(node.type["elt"], elt.type, - node.loc, elt.loc, kind="expects") + node.loc, elt.loc, makenotes) return node def visit_Subscript(self, node): @@ -279,7 +288,7 @@ class Inferencer(algorithm.Transformer): loc=node.loc) # TODO: support more than just lists self._unify(types.TList(node.type), node.value.type, - node.loc, node.value.loc, kind="expects") + node.loc, node.value.loc) return node def visit_IfExp(self, node): @@ -380,7 +389,7 @@ class Printer(algorithm.Visitor): def main(): import sys, fileinput, os - if sys.argv[1] == '+diag': + 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))) diff --git a/lit-test/py2llvm/typing/error_nonlocal_global.py b/lit-test/py2llvm/typing/error_nonlocal_global.py index 8586d418d..1242f56db 100644 --- a/lit-test/py2llvm/typing/error_nonlocal_global.py +++ b/lit-test/py2llvm/typing/error_nonlocal_global.py @@ -2,7 +2,7 @@ # RUN: OutputCheck %s --file-to-check=%t def a(): - # CHECK-L: ${LINE:+1}: error: can't declare name 'x' as nonlocal: it is not bound in any outer scope + # CHECK-L: ${LINE:+1}: error: cannot declare name 'x' as nonlocal: it is not bound in any outer scope nonlocal x x = 1 diff --git a/lit-test/py2llvm/typing/error_return.py b/lit-test/py2llvm/typing/error_return.py new file mode 100644 index 000000000..a84fe8d22 --- /dev/null +++ b/lit-test/py2llvm/typing/error_return.py @@ -0,0 +1,16 @@ +# RUN: %python -m artiq.py2llvm.typing +diag %s >%t +# RUN: OutputCheck %s --file-to-check=%t + +# CHECK-L: ${LINE:+2}: error: cannot unify int(width='a) with NoneType +# CHECK-L: ${LINE:+1}: note: function with return type int(width='a) +def a(): + return 1 + # CHECK-L: ${LINE:+1}: note: a statement returning NoneType + return + +# CHECK-L: ${LINE:+2}: error: cannot unify int(width='a) with list(elt='b) +# CHECK-L: ${LINE:+1}: note: function with return type int(width='a) +def b(): + return 1 + # CHECK-L: ${LINE:+1}: note: a statement returning list(elt='b) + return [] diff --git a/lit-test/py2llvm/typing/error_unify.py b/lit-test/py2llvm/typing/error_unify.py index 859e49205..9edf8d82a 100644 --- a/lit-test/py2llvm/typing/error_unify.py +++ b/lit-test/py2llvm/typing/error_unify.py @@ -4,5 +4,10 @@ a = 1 b = [] -# CHECK-L: ${LINE:+1}: fatal: cannot unify int(width='a) with list(elt='b) +# CHECK-L: ${LINE:+1}: error: cannot unify int(width='a) with list(elt='b) a = b + +# CHECK-L: ${LINE:+1}: error: cannot unify int(width='a) with list(elt='b) +[1, []] +# CHECK-L: note: a list of type list(elt=int(width='a)) +# CHECK-L: note: a list element of type list(elt='b) From 4b7d4c242500fb0d57c2ed8de207703586323f70 Mon Sep 17 00:00:00 2001 From: whitequark Date: Thu, 11 Jun 2015 04:20:33 +0300 Subject: [PATCH 023/369] Add support for BoolOp. --- artiq/py2llvm/typing.py | 26 ++++++++++++++++---------- lit-test/py2llvm/typing/error_unify.py | 5 +++++ lit-test/py2llvm/typing/unify.py | 3 +++ 3 files changed, 24 insertions(+), 10 deletions(-) diff --git a/artiq/py2llvm/typing.py b/artiq/py2llvm/typing.py index 1cfb50c20..a9ab62ffa 100644 --- a/artiq/py2llvm/typing.py +++ b/artiq/py2llvm/typing.py @@ -301,18 +301,24 @@ class Inferencer(algorithm.Transformer): def visit_BoolOp(self, node): node = self.generic_visit(node) - for value, op_loc in zip(node.values, node.op_locs): - def makenotes(printer, typea, typeb, loca, locb): - return [ - diagnostic.Diagnostic("note", - "py2llvm requires boolean operations to have boolean operands", {}, - op_loc) - ] - self._unify(value.type, types.TBool(), - value.loc, None, makenotes) - return asttyped.BoolOpT(type=types.TBool(), + node = asttyped.BoolOpT(type=types.TVar(), op=node.op, values=node.values, op_locs=node.op_locs, loc=node.loc) + def makenotes(printer, typea, typeb, loca, locb): + return [ + diagnostic.Diagnostic("note", + "an operand of type {typea}", + {"typea": printer.name(node.values[0].type)}, + node.values[0].loc), + diagnostic.Diagnostic("note", + "an operand of type {typeb}", + {"typeb": printer.name(typeb)}, + locb) + ] + for value in node.values: + self._unify(node.type, value.type, + node.loc, value.loc, makenotes) + return node # Visitors that just unify types # diff --git a/lit-test/py2llvm/typing/error_unify.py b/lit-test/py2llvm/typing/error_unify.py index 9edf8d82a..a1e8b1d8e 100644 --- a/lit-test/py2llvm/typing/error_unify.py +++ b/lit-test/py2llvm/typing/error_unify.py @@ -11,3 +11,8 @@ a = b [1, []] # CHECK-L: note: a list of type list(elt=int(width='a)) # CHECK-L: note: a list element of type list(elt='b) + +# CHECK-L: ${LINE:+1}: error: cannot unify int(width='a) with bool +1 and False +# CHECK-L: note: an operand of type int(width='a) +# CHECK-L: note: an operand of type bool diff --git a/lit-test/py2llvm/typing/unify.py b/lit-test/py2llvm/typing/unify.py index 2093cc3d0..8ea23dd0e 100644 --- a/lit-test/py2llvm/typing/unify.py +++ b/lit-test/py2llvm/typing/unify.py @@ -38,3 +38,6 @@ j += [1.0] True and False # CHECK-L: True:bool and False:bool:bool + +1 and 0 +# CHECK-L: 1:int(width='g) and 0:int(width='g):int(width='g) From e18ea0daae6b7046b02cbab2284fb3fd378c3d00 Mon Sep 17 00:00:00 2001 From: whitequark Date: Thu, 11 Jun 2015 04:22:31 +0300 Subject: [PATCH 024/369] Better error reporting for List. --- artiq/py2llvm/typing.py | 6 +++--- lit-test/py2llvm/typing/error_unify.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/artiq/py2llvm/typing.py b/artiq/py2llvm/typing.py index a9ab62ffa..fe5c2c800 100644 --- a/artiq/py2llvm/typing.py +++ b/artiq/py2llvm/typing.py @@ -268,9 +268,9 @@ class Inferencer(algorithm.Transformer): def makenotes(printer, typea, typeb, loca, locb): return [ diagnostic.Diagnostic("note", - "a list of type {typea}", - {"typea": printer.name(node.type)}, - loca), + "a list element of type {typea}", + {"typea": printer.name(node.elts[0].type)}, + node.elts[0].loc), diagnostic.Diagnostic("note", "a list element of type {typeb}", {"typeb": printer.name(typeb)}, diff --git a/lit-test/py2llvm/typing/error_unify.py b/lit-test/py2llvm/typing/error_unify.py index a1e8b1d8e..1140ba61f 100644 --- a/lit-test/py2llvm/typing/error_unify.py +++ b/lit-test/py2llvm/typing/error_unify.py @@ -9,7 +9,7 @@ a = b # CHECK-L: ${LINE:+1}: error: cannot unify int(width='a) with list(elt='b) [1, []] -# CHECK-L: note: a list of type list(elt=int(width='a)) +# CHECK-L: note: a list element of type int(width='a) # CHECK-L: note: a list element of type list(elt='b) # CHECK-L: ${LINE:+1}: error: cannot unify int(width='a) with bool From df686136f1830950fdc795920a28ad44a86c9fb0 Mon Sep 17 00:00:00 2001 From: whitequark Date: Thu, 11 Jun 2015 06:34:22 +0300 Subject: [PATCH 025/369] Separate inference and asttyped transformation. This allows to run inference several times on the same tree, as would be necessary when coercion nodes are added. --- artiq/py2llvm/typing.py | 122 +++++++++++++++++++++------------------- 1 file changed, 65 insertions(+), 57 deletions(-) diff --git a/artiq/py2llvm/typing.py b/artiq/py2llvm/typing.py index fe5c2c800..2fc33fc9d 100644 --- a/artiq/py2llvm/typing.py +++ b/artiq/py2llvm/typing.py @@ -166,6 +166,22 @@ class Inferencer(algorithm.Transformer): 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 _find_name(self, name, loc): for typing_env in reversed(self.env_stack): if name in typing_env: @@ -212,26 +228,6 @@ class Inferencer(algorithm.Transformer): return node - def visit_Return(self, node): - node = 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, types.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_Num(self, node): if isinstance(node.n, int): typ = types.TInt() @@ -265,63 +261,55 @@ class Inferencer(algorithm.Transformer): node = self.generic_visit(node) node = asttyped.ListT(type=types.TList(), elts=node.elts, ctx=node.ctx, loc=node.loc) - def makenotes(printer, typea, typeb, loca, locb): - return [ - diagnostic.Diagnostic("note", - "a list element of type {typea}", - {"typea": printer.name(node.elts[0].type)}, - node.elts[0].loc), - diagnostic.Diagnostic("note", - "a list element of type {typeb}", - {"typeb": printer.name(typeb)}, - locb) - ] - for elt in node.elts: - self._unify(node.type["elt"], elt.type, - node.loc, elt.loc, makenotes) - return node + 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, loc=node.loc) - # TODO: support more than just lists - self._unify(types.TList(node.type), node.value.type, - node.loc, node.value.loc) - return node + return self.visit(node) def visit_IfExp(self, node): node = self.generic_visit(node) - self._unify(node.body.type, node.orelse.type, - node.body.loc, node.orelse.loc) - return asttyped.IfExpT(type=node.body.type, + 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_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) - def makenotes(printer, typea, typeb, loca, locb): - return [ - diagnostic.Diagnostic("note", - "an operand of type {typea}", - {"typea": printer.name(node.values[0].type)}, - node.values[0].loc), - diagnostic.Diagnostic("note", - "an operand of type {typeb}", - {"typeb": printer.name(typeb)}, - locb) - ] - for value in node.values: - self._unify(node.type, value.type, - node.loc, value.loc, makenotes) - return node + return self.visit(node) # Visitors that just unify types # + def visit_ListT(self, node): + for elt in node.elts: + self._unify(node.type["elt"], elt.type, + node.loc, elt.loc, self._makenotes_elts(node.elts, "a list element")) + return node + + def visit_SubscriptT(self, node): + # TODO: support more than just lists + self._unify(types.TList(node.type), node.value.type, + node.loc, node.value.loc) + return node + + def visit_IfExpT(self, node): + self._unify(node.body.type, node.orelse.type, + node.body.loc, node.orelse.loc) + node.type = node.body.type + return node + + def visit_BoolOpT(self, node): + for value in node.values: + self._unify(node.type, value.type, + node.loc, value.loc, self._makenotes_elts(node.values, "an operand")) + return node + def visit_Assign(self, node): node = self.generic_visit(node) if len(node.targets) > 1: @@ -345,6 +333,26 @@ class Inferencer(algorithm.Transformer): node.target.loc, node.iter.loc) return node + def visit_Return(self, node): + node = 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, types.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) + # Unsupported visitors # def visit_unsupported(self, node): From c89bf6fae0359f590e243bfd1c725fbe04d58f82 Mon Sep 17 00:00:00 2001 From: whitequark Date: Fri, 12 Jun 2015 08:59:41 +0300 Subject: [PATCH 026/369] Add support for UnaryOp. --- artiq/py2llvm/types.py | 12 +++++++++ artiq/py2llvm/typing.py | 37 +++++++++++++++++++++++++- lit-test/py2llvm/typing/error_unify.py | 3 +++ lit-test/py2llvm/typing/unify.py | 6 +++++ 4 files changed, 57 insertions(+), 1 deletion(-) diff --git a/artiq/py2llvm/types.py b/artiq/py2llvm/types.py index 078051883..60768a349 100644 --- a/artiq/py2llvm/types.py +++ b/artiq/py2llvm/types.py @@ -180,6 +180,18 @@ def TList(elt=None): return TMono("list", {"elt": elt}) +def is_var(typ): + return isinstance(typ, TVar) + +def is_mono(typ, name, **params): + return isinstance(typ, TMono) and \ + typ.name == name and typ.params == params + +def is_numeric(typ): + return isinstance(typ, TMono) and \ + typ.name in ('int', 'float') + + class TypePrinter(object): """ A class that prints types using Python-like syntax and gives diff --git a/artiq/py2llvm/typing.py b/artiq/py2llvm/typing.py index 2fc33fc9d..943b5b6fe 100644 --- a/artiq/py2llvm/typing.py +++ b/artiq/py2llvm/typing.py @@ -284,6 +284,27 @@ class Inferencer(algorithm.Transformer): 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) + # Visitors that just unify types # def visit_ListT(self, node): @@ -310,6 +331,21 @@ class Inferencer(algorithm.Transformer): node.loc, value.loc, self._makenotes_elts(node.values, "an operand")) return node + def visit_UnaryOpT(self, node): + if isinstance(node.op, ast.Not): + node.type = types.TBool() + else: + operand_type = node.operand.type.find() + if types.is_numeric(operand_type): + node.type = operand_type + elif not types.is_var(operand_type): + diag = diagnostic.Diagnostic("error", + "expected operand to be of numeric type, not {type}", + {"type": types.TypePrinter().name(operand_type)}, + node.operand.loc) + self.engine.process(diag) + return node + def visit_Assign(self, node): node = self.generic_visit(node) if len(node.targets) > 1: @@ -375,7 +411,6 @@ class Inferencer(algorithm.Transformer): visit_SetComp = visit_unsupported visit_Str = visit_unsupported visit_Starred = visit_unsupported - visit_UnaryOp = visit_unsupported visit_Yield = visit_unsupported visit_YieldFrom = visit_unsupported diff --git a/lit-test/py2llvm/typing/error_unify.py b/lit-test/py2llvm/typing/error_unify.py index 1140ba61f..8f0faf23a 100644 --- a/lit-test/py2llvm/typing/error_unify.py +++ b/lit-test/py2llvm/typing/error_unify.py @@ -16,3 +16,6 @@ a = b 1 and False # CHECK-L: note: an operand of type int(width='a) # CHECK-L: note: an operand of type bool + +# CHECK-L: ${LINE:+1}: error: expected operand to be of numeric type, not list(elt='a) +~[] diff --git a/lit-test/py2llvm/typing/unify.py b/lit-test/py2llvm/typing/unify.py index 8ea23dd0e..64dfb20b7 100644 --- a/lit-test/py2llvm/typing/unify.py +++ b/lit-test/py2llvm/typing/unify.py @@ -41,3 +41,9 @@ True and False 1 and 0 # CHECK-L: 1:int(width='g) and 0:int(width='g):int(width='g) + +~1 +# CHECK-L: 1:int(width='h):int(width='h) + +not 1 +# CHECK-L: 1:int(width='i):bool From 3e2d104014a9276bcc5611ba70d44dd4db655c69 Mon Sep 17 00:00:00 2001 From: whitequark Date: Sat, 13 Jun 2015 09:28:40 +0300 Subject: [PATCH 027/369] Make typing.Inferencer idempotent. --- artiq/py2llvm/asttyped.py | 2 ++ artiq/py2llvm/types.py | 5 +++- artiq/py2llvm/typing.py | 56 ++++++++++++++++++++++++--------------- 3 files changed, 40 insertions(+), 23 deletions(-) diff --git a/artiq/py2llvm/asttyped.py b/artiq/py2llvm/asttyped.py index 848d2625a..415f7dcc0 100644 --- a/artiq/py2llvm/asttyped.py +++ b/artiq/py2llvm/asttyped.py @@ -29,6 +29,8 @@ class ClassDefT(ast.ClassDef, scoped): pass class FunctionDefT(ast.FunctionDef, scoped): pass +class ModuleT(ast.Module, scoped): + pass class AttributeT(ast.Attribute, commontyped): pass diff --git a/artiq/py2llvm/types.py b/artiq/py2llvm/types.py index 60768a349..f56aa34a8 100644 --- a/artiq/py2llvm/types.py +++ b/artiq/py2llvm/types.py @@ -184,8 +184,11 @@ def is_var(typ): return isinstance(typ, TVar) def is_mono(typ, name, **params): + params_match = True + for param in params: + params_match = params_match and typ.params[param] == params[param] return isinstance(typ, TMono) and \ - typ.name == name and typ.params == params + typ.name == name and params_match def is_numeric(typ): return isinstance(typ, TMono) and \ diff --git a/artiq/py2llvm/typing.py b/artiq/py2llvm/typing.py index 943b5b6fe..aa11f0e53 100644 --- a/artiq/py2llvm/typing.py +++ b/artiq/py2llvm/typing.py @@ -190,43 +190,35 @@ class Inferencer(algorithm.Transformer): "name '{name}' is not bound to anything", {"name":name}, loc) self.engine.process(diag) - def visit_root(self, node): - extractor = LocalExtractor(env_stack=self.env_stack, engine=self.engine) - extractor.visit(node) - self.env_stack.append(extractor.typing_env) - - return self.visit(node) - # Visitors that replace node with a typed node # - def visit_arg(self, node): - return asttyped.argT(type=self._find_name(node.arg, node.loc), - arg=node.arg, annotation=self.visit(node.annotation), - arg_loc=node.arg_loc, colon_loc=node.colon_loc, loc=node.loc) + 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) + return self.visit(node) def visit_FunctionDef(self, node): extractor = LocalExtractor(env_stack=self.env_stack, engine=self.engine) extractor.visit(node) - self.env_stack.append(extractor.typing_env) - node = asttyped.FunctionDefT( typing_env=extractor.typing_env, globals_in_scope=extractor.global_, 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) + return self.visit(node) - old_function, self.function = self.function, node - self.generic_visit(node) - self.function = old_function - - self.env_stack.pop() - - return node + def visit_arg(self, node): + return asttyped.argT(type=self._find_name(node.arg, node.loc), + arg=node.arg, annotation=self.visit(node.annotation), + arg_loc=node.arg_loc, colon_loc=node.colon_loc, loc=node.loc) def visit_Num(self, node): if isinstance(node.n, int): @@ -346,6 +338,26 @@ class Inferencer(algorithm.Transformer): self.engine.process(diag) return node + def visit_ModuleT(self, node): + self.env_stack.append(node.typing_env) + + node = self.generic_visit(node) + + self.env_stack.pop() + + return node + + def visit_FunctionDefT(self, node): + self.env_stack.append(node.typing_env) + old_function, self.function = self.function, node + + node = self.generic_visit(node) + + self.function = old_function + self.env_stack.pop() + + return node + def visit_Assign(self, node): node = self.generic_visit(node) if len(node.targets) > 1: @@ -455,7 +467,7 @@ def main(): buf = source.Buffer("".join(fileinput.input()), os.path.basename(fileinput.filename())) parsed, comments = parse_buffer(buf, engine=engine) - typed = Inferencer(engine=engine).visit_root(parsed) + typed = Inferencer(engine=engine).visit(parsed) printer = Printer(buf) printer.visit(typed) for comment in comments: From 56bba3009d57fa74a1f411388d3cc0abb71c5fa8 Mon Sep 17 00:00:00 2001 From: whitequark Date: Sat, 13 Jun 2015 09:34:31 +0300 Subject: [PATCH 028/369] Only build the master branch on Travis. --- .travis.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.travis.yml b/.travis.yml index bec5ffcff..157ad5aef 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,9 @@ language: python python: - '3.4' +branches: + only: + - master sudo: false env: global: From 61434a8da35a2c6654f423f9b9c478680bcb5bd5 Mon Sep 17 00:00:00 2001 From: whitequark Date: Sat, 13 Jun 2015 10:29:26 +0300 Subject: [PATCH 029/369] Split off builtins from types. builtins will contain attribute definitions as well. --- artiq/py2llvm/builtins.py | 63 +++++++++++++++++++++++++++++++++++++++ artiq/py2llvm/types.py | 58 ++--------------------------------- artiq/py2llvm/typing.py | 26 ++++++++-------- 3 files changed, 78 insertions(+), 69 deletions(-) create mode 100644 artiq/py2llvm/builtins.py diff --git a/artiq/py2llvm/builtins.py b/artiq/py2llvm/builtins.py new file mode 100644 index 000000000..e898eed28 --- /dev/null +++ b/artiq/py2llvm/builtins.py @@ -0,0 +1,63 @@ +""" +The :mod:`builtins` module contains the builtin Python and ARTIQ +types, such as int or float. +""" + +from . import types + +class TNone(types.TMono): + def __init__(self): + super().__init__("NoneType") + +class TBool(types.TMono): + def __init__(self): + super().__init__("bool") + +class TInt(types.TMono): + def __init__(self, width=None): + if width is None: + width = types.TVar() + super().__init__("int", {"width": width}) + +class TFloat(types.TMono): + def __init__(self): + super().__init__("float") + +class TTuple(types.Type): + """A tuple type.""" + + def __init__(self, elts=[]): + self.elts = elts + + def find(self): + return self + + def unify(self, other): + 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 __repr__(self): + return "TTuple(%s)" % (", ".join(map(repr, self.elts))) + + def __eq__(self, other): + return isinstance(other, TTuple) and \ + self.elts == other.elts + + def __ne__(self, other): + return not (self == other) + +class TList(types.TMono): + def __init__(self, elt=None): + if elt is None: + elt = types.TVar() + super().__init__("list", {"elt": elt}) + + +def is_numeric(typ): + return isinstance(typ, types.TMono) and \ + typ.name in ('int', 'float') diff --git a/artiq/py2llvm/types.py b/artiq/py2llvm/types.py index f56aa34a8..2397ea61f 100644 --- a/artiq/py2llvm/types.py +++ b/artiq/py2llvm/types.py @@ -37,6 +37,8 @@ class TVar(Type): folded into this class. """ + attributes = () + def __init__(self): self.parent = self @@ -99,34 +101,6 @@ class TMono(Type): def __ne__(self, other): return not (self == other) -class TTuple(Type): - """A tuple type.""" - - def __init__(self, elts=[]): - self.elts = elts - - def find(self): - return self - - def unify(self, other): - 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 __repr__(self): - return "TTuple(%s)" % (", ".join(map(repr, self.elts))) - - def __eq__(self, other): - return isinstance(other, TTuple) and \ - self.elts == other.elts - - def __ne__(self, other): - return not (self == other) - class TValue(Type): """ A type-level value (such as the integer denoting width of @@ -155,30 +129,6 @@ class TValue(Type): def __ne__(self, other): return not (self == other) -def TNone(): - """The type of None.""" - return TMono("NoneType") - -def TBool(): - """A boolean type.""" - return TMono("bool") - -def TInt(width=None): - """A generic integer type.""" - if width is None: - width = TVar() - return TMono("int", {"width": width}) - -def TFloat(): - """A double-precision floating point type.""" - return TMono("float") - -def TList(elt=None): - """A generic list type.""" - if elt is None: - elt = TVar() - return TMono("list", {"elt": elt}) - def is_var(typ): return isinstance(typ, TVar) @@ -190,10 +140,6 @@ def is_mono(typ, name, **params): return isinstance(typ, TMono) and \ typ.name == name and params_match -def is_numeric(typ): - return isinstance(typ, TMono) and \ - typ.name in ('int', 'float') - class TypePrinter(object): """ diff --git a/artiq/py2llvm/typing.py b/artiq/py2llvm/typing.py index aa11f0e53..70201a04f 100644 --- a/artiq/py2llvm/typing.py +++ b/artiq/py2llvm/typing.py @@ -1,5 +1,5 @@ from pythonparser import source, ast, algorithm, diagnostic, parse_buffer -from . import asttyped, types +from . import asttyped, types, builtins # This visitor will be called for every node with a scope, # i.e.: class, function, comprehension, lambda @@ -222,9 +222,9 @@ class Inferencer(algorithm.Transformer): def visit_Num(self, node): if isinstance(node.n, int): - typ = types.TInt() + typ = builtins.TInt() elif isinstance(node.n, float): - typ = types.TFloat() + typ = builtins.TFloat() else: diag = diagnostic.Diagnostic("fatal", "numeric type {type} is not supported", {"type": node.n.__class__.__name__}, @@ -239,19 +239,19 @@ class Inferencer(algorithm.Transformer): def visit_NameConstant(self, node): if node.value is True or node.value is False: - typ = types.TBool() + typ = builtins.TBool() elif node.value is None: - typ = types.TNone() + 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]), + return asttyped.TupleT(type=builtins.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=types.TList(), + node = asttyped.ListT(type=builtins.TList(), elts=node.elts, ctx=node.ctx, loc=node.loc) return self.visit(node) @@ -307,7 +307,7 @@ class Inferencer(algorithm.Transformer): def visit_SubscriptT(self, node): # TODO: support more than just lists - self._unify(types.TList(node.type), node.value.type, + self._unify(builtins.TList(node.type), node.value.type, node.loc, node.value.loc) return node @@ -325,10 +325,10 @@ class Inferencer(algorithm.Transformer): def visit_UnaryOpT(self, node): if isinstance(node.op, ast.Not): - node.type = types.TBool() + node.type = builtins.TBool() else: operand_type = node.operand.type.find() - if types.is_numeric(operand_type): + if builtins.is_numeric(operand_type): node.type = operand_type elif not types.is_var(operand_type): diag = diagnostic.Diagnostic("error", @@ -361,7 +361,7 @@ class Inferencer(algorithm.Transformer): def visit_Assign(self, node): node = self.generic_visit(node) if len(node.targets) > 1: - self._unify(types.TTuple([x.type for x in node.targets]), node.value.type, + self._unify(builtins.TTuple([x.type for x in node.targets]), node.value.type, node.targets[0].loc.join(node.targets[-1].loc), node.value.loc) else: self._unify(node.targets[0].type, node.value.type, @@ -377,7 +377,7 @@ class Inferencer(algorithm.Transformer): def visit_For(self, node): node = self.generic_visit(node) # TODO: support more than just lists - self._unify(TList(node.target.type), node.iter.type, + self._unify(builtins.TList(node.target.type), node.iter.type, node.target.loc, node.iter.loc) return node @@ -395,7 +395,7 @@ class Inferencer(algorithm.Transformer): node.loc) ] if node.value is None: - self._unify(self.function.return_type, types.TNone(), + 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, From 4c95647162ee119a87ab7181a062848d5e88a4b8 Mon Sep 17 00:00:00 2001 From: whitequark Date: Sat, 13 Jun 2015 11:03:33 +0300 Subject: [PATCH 030/369] Split ASTTypedRewriter off Inferencer. --- artiq/py2llvm/typing.py | 209 +++++++++++++++++++--------------------- 1 file changed, 101 insertions(+), 108 deletions(-) diff --git a/artiq/py2llvm/typing.py b/artiq/py2llvm/typing.py index 70201a04f..2d15ae80c 100644 --- a/artiq/py2llvm/typing.py +++ b/artiq/py2llvm/typing.py @@ -126,61 +126,10 @@ class LocalExtractor(algorithm.Visitor): self.visit(stmt) -class Inferencer(algorithm.Transformer): +class ASTTypedRewriter(algorithm.Transformer): def __init__(self, engine): self.engine = engine self.env_stack = [] - self.function = None # currently visited function - - def _unify(self, typea, typeb, loca, locb, makenotes=None): - 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), - 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(): - diag = diagnostic.Diagnostic("error", - "cannot unify {typea} with {typeb}", - {"typea": printer.name(typea), "typeb": printer.name(typeb)}, - loca, highlights, notes) - else: # give more detail - diag = diagnostic.Diagnostic("error", - "cannot unify {typea} with {typeb}: {fraga} is incompatible with {fragb}", - {"typea": printer.name(typea), "typeb": printer.name(typeb), - "fraga": printer.name(e.typea), "fragb": printer.name(e.typeb)}, - 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 _find_name(self, name, loc): for typing_env in reversed(self.env_stack): @@ -199,7 +148,12 @@ class Inferencer(algorithm.Transformer): node = asttyped.ModuleT( typing_env=extractor.typing_env, globals_in_scope=extractor.global_, body=node.body, loc=node.loc) - return self.visit(node) + + 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) @@ -213,7 +167,12 @@ class Inferencer(algorithm.Transformer): 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) - return self.visit(node) + + try: + self.env_stack.append(node.typing_env) + return self.generic_visit(node) + finally: + self.env_stack.pop() def visit_arg(self, node): return asttyped.argT(type=self._find_name(node.arg, node.loc), @@ -297,31 +256,107 @@ class Inferencer(algorithm.Transformer): loc=node.loc) return self.visit(node) - # Visitors that just unify types + # Unsupported visitors # + def visit_unsupported(self, node): + diag = diagnostic.Diagnostic("fatal", + "this syntax is not supported", {}, + node.loc) + self.engine.process(diag) + + # expr + visit_Attribute = visit_unsupported + visit_BinOp = visit_unsupported + visit_Call = visit_unsupported + visit_Compare = visit_unsupported + visit_Dict = visit_unsupported + visit_DictComp = visit_unsupported + visit_Ellipsis = visit_unsupported + visit_GeneratorExp = visit_unsupported + visit_Lambda = visit_unsupported + visit_ListComp = visit_unsupported + visit_Set = visit_unsupported + visit_SetComp = visit_unsupported + visit_Str = visit_unsupported + visit_Starred = visit_unsupported + visit_Yield = visit_unsupported + visit_YieldFrom = visit_unsupported + +class Inferencer(algorithm.Visitor): + def __init__(self, engine): + self.engine = engine + # currently visited function, for Return inference + self.function = None + + def _unify(self, typea, typeb, loca, locb, makenotes=None): + 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), + 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(): + diag = diagnostic.Diagnostic("error", + "cannot unify {typea} with {typeb}", + {"typea": printer.name(typea), "typeb": printer.name(typeb)}, + loca, highlights, notes) + else: # give more detail + diag = diagnostic.Diagnostic("error", + "cannot unify {typea} with {typeb}: {fraga} is incompatible with {fragb}", + {"typea": printer.name(typea), "typeb": printer.name(typeb), + "fraga": printer.name(e.typea), "fragb": printer.name(e.typeb)}, + 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): for elt in node.elts: self._unify(node.type["elt"], elt.type, node.loc, elt.loc, self._makenotes_elts(node.elts, "a list element")) - return node def visit_SubscriptT(self, node): # TODO: support more than just lists self._unify(builtins.TList(node.type), node.value.type, node.loc, node.value.loc) - return node def visit_IfExpT(self, node): self._unify(node.body.type, node.orelse.type, node.body.loc, node.orelse.loc) node.type = node.body.type - return node def visit_BoolOpT(self, node): for value in node.values: self._unify(node.type, value.type, node.loc, value.loc, self._makenotes_elts(node.values, "an operand")) - return node def visit_UnaryOpT(self, node): if isinstance(node.op, ast.Not): @@ -336,53 +371,34 @@ class Inferencer(algorithm.Transformer): {"type": types.TypePrinter().name(operand_type)}, node.operand.loc) self.engine.process(diag) - return node - - def visit_ModuleT(self, node): - self.env_stack.append(node.typing_env) - - node = self.generic_visit(node) - - self.env_stack.pop() - - return node def visit_FunctionDefT(self, node): - self.env_stack.append(node.typing_env) old_function, self.function = self.function, node - node = self.generic_visit(node) - self.function = old_function - self.env_stack.pop() - - return node def visit_Assign(self, node): - node = self.generic_visit(node) + self.generic_visit(node) if len(node.targets) > 1: self._unify(builtins.TTuple([x.type for x in node.targets]), node.value.type, node.targets[0].loc.join(node.targets[-1].loc), node.value.loc) else: self._unify(node.targets[0].type, node.value.type, node.targets[0].loc, node.value.loc) - return node def visit_AugAssign(self, node): - node = self.generic_visit(node) + self.generic_visit(node) self._unify(node.target.type, node.value.type, node.target.loc, node.value.loc) - return node def visit_For(self, node): - node = self.generic_visit(node) + self.generic_visit(node) # TODO: support more than just lists self._unify(builtins.TList(node.target.type), node.iter.type, node.target.loc, node.iter.loc) - return node def visit_Return(self, node): - node = self.generic_visit(node) + self.generic_visit(node) def makenotes(printer, typea, typeb, loca, locb): return [ diagnostic.Diagnostic("note", @@ -401,31 +417,6 @@ class Inferencer(algorithm.Transformer): self._unify(self.function.return_type, node.value.type, self.function.name_loc, node.value.loc, makenotes) - # Unsupported visitors - # - def visit_unsupported(self, node): - diag = diagnostic.Diagnostic("fatal", - "this syntax is not supported", {}, - node.loc) - self.engine.process(diag) - - visit_Attribute = visit_unsupported - visit_BinOp = visit_unsupported - visit_Call = visit_unsupported - visit_Compare = visit_unsupported - visit_Dict = visit_unsupported - visit_DictComp = visit_unsupported - visit_Ellipsis = visit_unsupported - visit_GeneratorExp = visit_unsupported - visit_Lambda = visit_unsupported - visit_ListComp = visit_unsupported - visit_Set = visit_unsupported - visit_SetComp = visit_unsupported - visit_Str = visit_unsupported - visit_Starred = visit_unsupported - visit_Yield = visit_unsupported - visit_YieldFrom = visit_unsupported - class Printer(algorithm.Visitor): def __init__(self, buf): self.rewriter = source.Rewriter(buf) @@ -467,7 +458,9 @@ def main(): buf = source.Buffer("".join(fileinput.input()), os.path.basename(fileinput.filename())) parsed, comments = parse_buffer(buf, engine=engine) - typed = Inferencer(engine=engine).visit(parsed) + typed = ASTTypedRewriter(engine=engine).visit(parsed) + Inferencer(engine=engine).visit(typed) + printer = Printer(buf) printer.visit(typed) for comment in comments: From 55551714fabbab597ca4615f5c53af6d93e95fa1 Mon Sep 17 00:00:00 2001 From: whitequark Date: Sat, 13 Jun 2015 11:33:15 +0300 Subject: [PATCH 031/369] Error out on unsupported statements. --- artiq/py2llvm/typing.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/artiq/py2llvm/typing.py b/artiq/py2llvm/typing.py index 2d15ae80c..f7f754195 100644 --- a/artiq/py2llvm/typing.py +++ b/artiq/py2llvm/typing.py @@ -282,6 +282,19 @@ class ASTTypedRewriter(algorithm.Transformer): visit_Yield = visit_unsupported visit_YieldFrom = visit_unsupported + # stmt + visit_Assert = visit_unsupported + visit_Break = visit_unsupported + visit_ClassDef = visit_unsupported + visit_Continue = visit_unsupported + visit_Delete = visit_unsupported + visit_Import = visit_unsupported + visit_ImportFrom = visit_unsupported + visit_Raise = visit_unsupported + visit_Try = visit_unsupported + visit_With = visit_unsupported + + class Inferencer(algorithm.Visitor): def __init__(self, engine): self.engine = engine From 4b4805265d148e4c36aeca3b5c46e0c3b8a3584f Mon Sep 17 00:00:00 2001 From: whitequark Date: Sat, 13 Jun 2015 12:07:46 +0300 Subject: [PATCH 032/369] Add support for Break and Continue. --- artiq/py2llvm/typing.py | 46 +++++++++++++++---- lit-test/py2llvm/typing/error_control_flow.py | 19 ++++++++ 2 files changed, 56 insertions(+), 9 deletions(-) create mode 100644 lit-test/py2llvm/typing/error_control_flow.py diff --git a/artiq/py2llvm/typing.py b/artiq/py2llvm/typing.py index f7f754195..87d68e797 100644 --- a/artiq/py2llvm/typing.py +++ b/artiq/py2llvm/typing.py @@ -284,9 +284,7 @@ class ASTTypedRewriter(algorithm.Transformer): # stmt visit_Assert = visit_unsupported - visit_Break = visit_unsupported visit_ClassDef = visit_unsupported - visit_Continue = visit_unsupported visit_Delete = visit_unsupported visit_Import = visit_unsupported visit_ImportFrom = visit_unsupported @@ -298,8 +296,8 @@ class ASTTypedRewriter(algorithm.Transformer): class Inferencer(algorithm.Visitor): def __init__(self, engine): self.engine = engine - # currently visited function, for Return inference - self.function = None + self.function = None # currently visited function, for Return inference + self.in_loop = False def _unify(self, typea, typeb, loca, locb, makenotes=None): try: @@ -385,11 +383,6 @@ class Inferencer(algorithm.Visitor): node.operand.loc) self.engine.process(diag) - def visit_FunctionDefT(self, node): - old_function, self.function = self.function, node - node = self.generic_visit(node) - self.function = old_function - def visit_Assign(self, node): self.generic_visit(node) if len(node.targets) > 1: @@ -405,12 +398,47 @@ class Inferencer(algorithm.Visitor): node.target.loc, node.value.loc) def visit_For(self, node): + old_in_loop, self.in_loop = self.in_loop, True self.generic_visit(node) + self.in_loop = old_in_loop # TODO: support more than just lists self._unify(builtins.TList(node.target.type), node.iter.type, node.target.loc, node.iter.loc) + 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_FunctionDefT(self, node): + old_function, self.function = self.function, node + old_in_loop, self.in_loop = self.in_loop, False + self.generic_visit(node) + self.function = old_function + self.in_loop = old_in_loop + 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.generic_visit(node) def makenotes(printer, typea, typeb, loca, locb): return [ diff --git a/lit-test/py2llvm/typing/error_control_flow.py b/lit-test/py2llvm/typing/error_control_flow.py new file mode 100644 index 000000000..ea72347dd --- /dev/null +++ b/lit-test/py2llvm/typing/error_control_flow.py @@ -0,0 +1,19 @@ +# RUN: %python -m artiq.py2llvm.typing +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 From de6dff94cd13ab862a4f76a443bb41d9fd6cdb69 Mon Sep 17 00:00:00 2001 From: whitequark Date: Sat, 13 Jun 2015 12:58:45 +0300 Subject: [PATCH 033/369] GeneratorExp also includes assignment context. --- artiq/py2llvm/typing.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/artiq/py2llvm/typing.py b/artiq/py2llvm/typing.py index 87d68e797..ca7933bad 100644 --- a/artiq/py2llvm/typing.py +++ b/artiq/py2llvm/typing.py @@ -58,12 +58,13 @@ class LocalExtractor(algorithm.Visitor): self.in_root = True self.generic_visit(node) - visit_ClassDef = visit_root # don't look at inner scopes - visit_FunctionDef = visit_root - visit_Lambda = visit_root - visit_DictComp = visit_root - visit_ListComp = visit_root - visit_SetComp = visit_root + visit_ClassDef = visit_root # don't look at inner scopes + visit_FunctionDef = visit_root + visit_Lambda = visit_root + visit_DictComp = visit_root + visit_ListComp = visit_root + visit_SetComp = visit_root + visit_GeneratorExp = visit_root def _assignable(self, name): if name not in self.typing_env and name not in self.nonlocal_: From 1c48874a2a2ed664ead1ec0f6e0b5fc1ad4ed869 Mon Sep 17 00:00:00 2001 From: whitequark Date: Sat, 13 Jun 2015 13:08:16 +0300 Subject: [PATCH 034/369] Documentation. --- artiq/py2llvm/typing.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/artiq/py2llvm/typing.py b/artiq/py2llvm/typing.py index ca7933bad..db00cdcb8 100644 --- a/artiq/py2llvm/typing.py +++ b/artiq/py2llvm/typing.py @@ -128,6 +128,15 @@ class LocalExtractor(algorithm.Visitor): 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): self.engine = engine self.env_stack = [] @@ -295,6 +304,15 @@ class ASTTypedRewriter(algorithm.Transformer): 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 @@ -460,6 +478,16 @@ class Inferencer(algorithm.Visitor): self.function.name_loc, node.value.loc, makenotes) 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() From 23f33d7239d318674e5fa050f8c51592f82a053f Mon Sep 17 00:00:00 2001 From: whitequark Date: Sat, 13 Jun 2015 13:45:09 +0300 Subject: [PATCH 035/369] Invert operand should be integer. --- artiq/py2llvm/builtins.py | 6 ++++++ artiq/py2llvm/typing.py | 18 ++++++++++++++---- lit-test/py2llvm/typing/error_unify.py | 7 +++++-- 3 files changed, 25 insertions(+), 6 deletions(-) diff --git a/artiq/py2llvm/builtins.py b/artiq/py2llvm/builtins.py index e898eed28..c547ef374 100644 --- a/artiq/py2llvm/builtins.py +++ b/artiq/py2llvm/builtins.py @@ -58,6 +58,12 @@ class TList(types.TMono): super().__init__("list", {"elt": elt}) +def is_int(typ, width=None): + if width: + return types.is_mono(typ, "int", {"width": width}) + else: + return types.is_mono(typ, "int") + def is_numeric(typ): return isinstance(typ, types.TMono) and \ typ.name in ('int', 'float') diff --git a/artiq/py2llvm/typing.py b/artiq/py2llvm/typing.py index db00cdcb8..33e8c4dc2 100644 --- a/artiq/py2llvm/typing.py +++ b/artiq/py2llvm/typing.py @@ -389,16 +389,26 @@ class Inferencer(algorithm.Visitor): node.loc, value.loc, self._makenotes_elts(node.values, "an operand")) def visit_UnaryOpT(self, node): + operand_type = node.operand.type.find() if isinstance(node.op, ast.Not): node.type = builtins.TBool() - else: - operand_type = node.operand.type.find() + elif isinstance(node.op, ast.Invert): + if builtins.is_int(operand_type): + node.type = operand_type + 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 builtins.is_numeric(operand_type): node.type = operand_type elif not types.is_var(operand_type): diag = diagnostic.Diagnostic("error", - "expected operand to be of numeric type, not {type}", - {"type": types.TypePrinter().name(operand_type)}, + "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) diff --git a/lit-test/py2llvm/typing/error_unify.py b/lit-test/py2llvm/typing/error_unify.py index 8f0faf23a..73641b8c6 100644 --- a/lit-test/py2llvm/typing/error_unify.py +++ b/lit-test/py2llvm/typing/error_unify.py @@ -17,5 +17,8 @@ a = b # CHECK-L: note: an operand of type int(width='a) # CHECK-L: note: an operand of type bool -# CHECK-L: ${LINE:+1}: error: expected operand to be of numeric type, not list(elt='a) -~[] +# 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 From faaf189961cf9850bc4b246e14f0bf5f8ce503a5 Mon Sep 17 00:00:00 2001 From: whitequark Date: Sat, 13 Jun 2015 13:50:56 +0300 Subject: [PATCH 036/369] Add support for Attribute. --- artiq/py2llvm/builtins.py | 2 ++ artiq/py2llvm/types.py | 4 +-- artiq/py2llvm/typing.py | 35 ++++++++++++++++++++------ lit-test/py2llvm/typing/error_unify.py | 3 +++ 4 files changed, 34 insertions(+), 10 deletions(-) diff --git a/artiq/py2llvm/builtins.py b/artiq/py2llvm/builtins.py index c547ef374..84229cc6c 100644 --- a/artiq/py2llvm/builtins.py +++ b/artiq/py2llvm/builtins.py @@ -26,6 +26,8 @@ class TFloat(types.TMono): class TTuple(types.Type): """A tuple type.""" + attributes = {} + def __init__(self, elts=[]): self.elts = elts diff --git a/artiq/py2llvm/types.py b/artiq/py2llvm/types.py index 2397ea61f..cce235ed8 100644 --- a/artiq/py2llvm/types.py +++ b/artiq/py2llvm/types.py @@ -37,8 +37,6 @@ class TVar(Type): folded into this class. """ - attributes = () - def __init__(self): self.parent = self @@ -71,6 +69,8 @@ class TVar(Type): class TMono(Type): """A monomorphic type, possibly parametric.""" + attributes = {} + def __init__(self, name, params={}): self.name, self.params = name, params diff --git a/artiq/py2llvm/typing.py b/artiq/py2llvm/typing.py index 33e8c4dc2..a35b763f6 100644 --- a/artiq/py2llvm/typing.py +++ b/artiq/py2llvm/typing.py @@ -224,6 +224,13 @@ class ASTTypedRewriter(algorithm.Transformer): elts=node.elts, ctx=node.ctx, 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_Subscript(self, node): node = self.generic_visit(node) node = asttyped.SubscriptT(type=types.TVar(), @@ -231,13 +238,6 @@ class ASTTypedRewriter(algorithm.Transformer): 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_BoolOp(self, node): node = self.generic_visit(node) node = asttyped.BoolOpT(type=types.TVar(), @@ -266,6 +266,13 @@ class ASTTypedRewriter(algorithm.Transformer): 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) + # Unsupported visitors # def visit_unsupported(self, node): @@ -275,7 +282,6 @@ class ASTTypedRewriter(algorithm.Transformer): self.engine.process(diag) # expr - visit_Attribute = visit_unsupported visit_BinOp = visit_unsupported visit_Call = visit_unsupported visit_Compare = visit_unsupported @@ -373,6 +379,19 @@ class Inferencer(algorithm.Visitor): self._unify(node.type["elt"], elt.type, node.loc, elt.loc, self._makenotes_elts(node.elts, "a list element")) + def visit_AttributeT(self, node): + object_type = node.value.type.find() + if not types.is_var(object_type): + if node.attr in object_type.attributes: + # assumes no free type variables in .attributes + node.type = object_type.attributes[node.attr] + else: + diag = diagnostic.Diagnostic("error", + "type {type} does not have an attribute '{attr}'", + {"type": types.TypePrinter().name(object_type), "attr": node.attr}, + node.attr_loc, [node.value.loc]) + self.engine.process(diag) + def visit_SubscriptT(self, node): # TODO: support more than just lists self._unify(builtins.TList(node.type), node.value.type, diff --git a/lit-test/py2llvm/typing/error_unify.py b/lit-test/py2llvm/typing/error_unify.py index 73641b8c6..d268837e1 100644 --- a/lit-test/py2llvm/typing/error_unify.py +++ b/lit-test/py2llvm/typing/error_unify.py @@ -22,3 +22,6 @@ a = b # CHECK-L: ${LINE:+1}: error: expected ~ operand to be of integer type, not float ~1.0 + +# CHECK-L: ${LINE:+1}: error: type int(width='a) does not have an attribute 'x' +(1).x From 7b78e7de673e8f65d994c4822eb05b478c54b698 Mon Sep 17 00:00:00 2001 From: whitequark Date: Sun, 14 Jun 2015 12:07:13 +0300 Subject: [PATCH 037/369] Add support for BinOp. --- artiq/py2llvm/asttyped.py | 5 + artiq/py2llvm/builtins.py | 55 +++---- artiq/py2llvm/types.py | 49 +++++- artiq/py2llvm/typing.py | 194 ++++++++++++++++++++++-- lit-test/py2llvm/typing/coerce.py | 26 ++++ lit-test/py2llvm/typing/error_coerce.py | 35 +++++ lit-test/py2llvm/typing/error_unify.py | 4 +- 7 files changed, 325 insertions(+), 43 deletions(-) create mode 100644 lit-test/py2llvm/typing/coerce.py create mode 100644 lit-test/py2llvm/typing/error_coerce.py diff --git a/artiq/py2llvm/asttyped.py b/artiq/py2llvm/asttyped.py index 415f7dcc0..e96edd641 100644 --- a/artiq/py2llvm/asttyped.py +++ b/artiq/py2llvm/asttyped.py @@ -22,6 +22,7 @@ class scoped(object): list of variables resolved as globals """ +# Typed versions of untyped nodes class argT(ast.arg, commontyped): pass @@ -82,3 +83,7 @@ class YieldT(ast.Yield, commontyped): pass class YieldFromT(ast.YieldFrom, commontyped): pass + +# Novel typed nodes +class CoerceT(ast.expr, commontyped): + _fields = ('expr',) # other_expr deliberately not in _fields diff --git a/artiq/py2llvm/builtins.py b/artiq/py2llvm/builtins.py index 84229cc6c..569b85cb2 100644 --- a/artiq/py2llvm/builtins.py +++ b/artiq/py2llvm/builtins.py @@ -23,36 +23,6 @@ class TFloat(types.TMono): def __init__(self): super().__init__("float") -class TTuple(types.Type): - """A tuple type.""" - - attributes = {} - - def __init__(self, elts=[]): - self.elts = elts - - def find(self): - return self - - def unify(self, other): - 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 __repr__(self): - return "TTuple(%s)" % (", ".join(map(repr, self.elts))) - - def __eq__(self, other): - return isinstance(other, TTuple) and \ - self.elts == other.elts - - def __ne__(self, other): - return not (self == other) - class TList(types.TMono): def __init__(self, elt=None): if elt is None: @@ -60,12 +30,37 @@ class TList(types.TMono): super().__init__("list", {"elt": elt}) +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: return types.is_mono(typ, "int", {"width": width}) else: return types.is_mono(typ, "int") +def get_int_width(typ): + if is_int(typ): + return types.get_value(typ["width"]) + +def is_float(typ): + return types.is_mono(typ, "float") + 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: + return types.is_mono(typ, "list", {"elt": elt}) + else: + return types.is_mono(typ, "list") + +def is_collection(typ): + typ = typ.find() + return isinstance(typ, types.TTuple) or \ + types.is_mono(typ, "list") diff --git a/artiq/py2llvm/types.py b/artiq/py2llvm/types.py index cce235ed8..97aff58ca 100644 --- a/artiq/py2llvm/types.py +++ b/artiq/py2llvm/types.py @@ -101,6 +101,36 @@ class TMono(Type): def __ne__(self, other): return not (self == other) +class TTuple(Type): + """A tuple type.""" + + attributes = {} + + def __init__(self, elts=[]): + self.elts = elts + + def find(self): + return self + + def unify(self, other): + 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 __repr__(self): + return "TTuple(%s)" % (", ".join(map(repr, self.elts))) + + def __eq__(self, other): + return isinstance(other, TTuple) and \ + self.elts == other.elts + + def __ne__(self, other): + return not (self == other) + class TValue(Type): """ A type-level value (such as the integer denoting width of @@ -131,15 +161,32 @@ class TValue(Type): def is_var(typ): - return isinstance(typ, TVar) + return isinstance(typ.find(), TVar) def is_mono(typ, name, **params): + typ = typ.find() params_match = True for param in params: params_match = params_match and typ.params[param] == params[param] return isinstance(typ, TMono) and \ typ.name == name and params_match +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 get_value(typ): + typ = typ.find() + if isinstance(typ, TVar): + return None + elif isinstance(typ, TValue): + return typ.value + else: + assert False class TypePrinter(object): """ diff --git a/artiq/py2llvm/typing.py b/artiq/py2llvm/typing.py index a35b763f6..0f87dcd17 100644 --- a/artiq/py2llvm/typing.py +++ b/artiq/py2llvm/typing.py @@ -215,7 +215,7 @@ class ASTTypedRewriter(algorithm.Transformer): def visit_Tuple(self, node): node = self.generic_visit(node) - return asttyped.TupleT(type=builtins.TTuple([x.type for x in node.elts]), + 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): @@ -282,7 +282,6 @@ class ASTTypedRewriter(algorithm.Transformer): self.engine.process(diag) # expr - visit_BinOp = visit_unsupported visit_Call = visit_unsupported visit_Compare = visit_unsupported visit_Dict = visit_unsupported @@ -375,16 +374,18 @@ class Inferencer(algorithm.Visitor): return makenotes def visit_ListT(self, node): + self.generic_visit(node) for elt in node.elts: self._unify(node.type["elt"], elt.type, node.loc, elt.loc, self._makenotes_elts(node.elts, "a list element")) def visit_AttributeT(self, node): + self.generic_visit(node) object_type = node.value.type.find() if not types.is_var(object_type): if node.attr in object_type.attributes: # assumes no free type variables in .attributes - node.type = object_type.attributes[node.attr] + node.type.unify(object_type.attributes[node.attr]) # should never fail else: diag = diagnostic.Diagnostic("error", "type {type} does not have an attribute '{attr}'", @@ -393,48 +394,221 @@ class Inferencer(algorithm.Visitor): self.engine.process(diag) def visit_SubscriptT(self, node): + self.generic_visit(node) # TODO: support more than just lists self._unify(builtins.TList(node.type), node.value.type, node.loc, node.value.loc) def visit_IfExpT(self, node): + self.generic_visit(node) self._unify(node.body.type, node.orelse.type, node.body.loc, node.orelse.loc) - node.type = node.body.type + node.type.unify(node.body.type) # should never fail 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): - node.type = builtins.TBool() + node.type.unify(builtins.TBool()) # should never fail elif isinstance(node.op, ast.Invert): if builtins.is_int(operand_type): - node.type = operand_type + node.type.unify(operand_type) # should never fail elif not types.is_var(operand_type): diag = diagnostic.Diagnostic("error", - "expected ~ operand to be of integer type, not {type}", + "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 builtins.is_numeric(operand_type): - node.type = operand_type + node.type.unify(operand_type) # should never fail elif not types.is_var(operand_type): diag = diagnostic.Diagnostic("error", - "expected unary {op} operand to be of numeric type, not {type}", + "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.expr.type): + pass + else: + printer = types.TypePrinter() + note = diagnostic.Diagnostic("note", + "expression that required coercion to {typeb}", + {"typeb": printer.name(node.type)}, + node.other_expr.loc) + diag = diagnostic.Diagnostic("error", + "cannot coerce {typea} to {typeb}", + {"typea": printer.name(node.expr.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 + else: + node = asttyped.CoerceT(type=typ, expr=coerced_node, other_expr=other_node, + loc=coerced_node.loc) + self.visit(node) + return node + + def _coerce_numeric(self, return_type, left, right): + # Implements the coercion protocol. + # See https://docs.python.org/3/library/stdtypes.html#numeric-types-int-float-complex. + if builtins.is_float(left.type) or builtins.is_float(right.type): + typ = builtins.TFloat() + elif builtins.is_int(left.type) or builtins.is_int(right.type): + left_width, right_width = \ + builtins.get_int_width(left.type), builtins.get_int_width(left.type) + if left_width and right_width: + typ = builtins.TInt(types.TValue(max(left_width, right_width))) + else: + typ = builtins.TInt() + elif types.is_var(left.type) or types.is_var(right.type): # not enough info yet + return left, right + else: # conflicting types + printer = types.TypePrinter() + note1 = diagnostic.Diagnostic("note", + "expression of type {typea}", {"typea": printer.name(left.type)}, + left.loc) + note2 = diagnostic.Diagnostic("note", + "expression of type {typeb}", {"typeb": printer.name(right.type)}, + right.loc) + diag = diagnostic.Diagnostic("error", + "cannot coerce {typea} and {typeb} to a common numeric type", + {"typea": printer.name(left.type), "typeb": printer.name(right.type)}, + left.loc, [right.loc], + [note1, note2]) + self.engine.process(diag) + return left, right + + # On 1st invocation, return_type is always a type variable. + # On further invocations, coerce will only ever refine the type, + # so this should never fail. + return_type.unify(typ) + + return self._coerce_one(typ, left, other_node=right), \ + self._coerce_one(typ, right, other_node=left) + + 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 visit_BinOpT(self, node): + self.generic_visit(node) + if isinstance(node.op, (ast.BitAnd, ast.BitOr, ast.BitXor, + ast.LShift, ast.RShift)): + # bitwise operators require integers + for operand in (node.left, node.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": node.op.loc.source(), + "type": types.TypePrinter().name(operand.type)}, + node.op.loc, [operand.loc]) + self.engine.process(diag) + return + + node.left, node.right = \ + self._coerce_numeric(node.type, node.left, node.right) + elif isinstance(node.op, ast.Add): + # add works on numbers and also collections + if builtins.is_collection(node.left.type) or builtins.is_collection(node.right.type): + collection, other = \ + self._order_by_pred(builtins.is_collection, node.left, node.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 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}, + node.op.loc, [other.loc, collection.loc], + [note1, note2]) + self.engine.process(diag) + return + + if types.is_tuple(collection.type): + # should never fail + node.type.unify(types.TTuple(node.left.type.find().elts + + node.right.type.find().elts)) + elif builtins.is_list(collection.type): + self._unify(node.left.type, node.right.type, + node.left.loc, node.right.loc) + node.type.unify(node.left.type) # should never fail + else: + node.left, node.right = \ + self._coerce_numeric(node.type, node.left, node.right) + elif isinstance(node.op, ast.Mult): + # mult works on numbers and also number & collection + if types.is_tuple(node.left.type) or types.is_tuple(node.right.type): + tuple_, other = self._order_by_pred(types.is_tuple, node.left, node.right) + diag = diagnostic.Diagnostic("error", + "py2llvm does not support passing tuples to '*'", {}, + node.op.loc, [tuple_.loc]) + self.engine.process(diag) + elif builtins.is_list(node.left.type) or builtins.is_list(node.right.type): + list_, other = self._order_by_pred(builtins.is_list, node.left, node.right) + if not builtins.is_int(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", {}, + node.op.loc, [list_.loc, other.loc], + [note1, note2]) + self.engine.process(diag) + return + node.type.unify(list_.type) + else: + node.left, node.right = \ + self._coerce_numeric(node.type, node.left, node.right) + elif isinstance(node.op, (ast.Div, ast.FloorDiv, ast.Mod, ast.Pow, ast.Sub)): + # numeric operators work on any kind of number + node.left, node.right = \ + self._coerce_numeric(node.type, node.left, node.right) + else: # MatMult + diag = diagnostic.Diagnostic("error", + "operator '{op}' is not supported", {"op": node.op.loc.source()}, + node.op.loc) + self.engine.process(diag) + return + def visit_Assign(self, node): self.generic_visit(node) if len(node.targets) > 1: - self._unify(builtins.TTuple([x.type for x in node.targets]), node.value.type, + self._unify(types.TTuple([x.type for x in node.targets]), node.value.type, node.targets[0].loc.join(node.targets[-1].loc), node.value.loc) else: self._unify(node.targets[0].type, node.value.type, diff --git a/lit-test/py2llvm/typing/coerce.py b/lit-test/py2llvm/typing/coerce.py new file mode 100644 index 000000000..ac33002d9 --- /dev/null +++ b/lit-test/py2llvm/typing/coerce.py @@ -0,0 +1,26 @@ +# RUN: %python -m artiq.py2llvm.typing %s >%t +# RUN: OutputCheck %s --file-to-check=%t + +1 | 2 +# CHECK-L: 1:int(width='a):int(width='b) | 2:int(width='c):int(width='b):int(width='b) + +1 + 2 +# CHECK-L: 1:int(width='d):int(width='e) + 2:int(width='f):int(width='e):int(width='e) + +(1,) + (2.0,) +# CHECK-L: (1:int(width='g),):(int(width='g),) + (2.0:float,):(float,):(int(width='g), float) + +[1] + [2] +# CHECK-L: [1:int(width='h)]:list(elt=int(width='h)) + [2:int(width='h)]:list(elt=int(width='h)):list(elt=int(width='h)) + +1 * 2 +# CHECK-L: 1:int(width='i):int(width='j) * 2:int(width='k):int(width='j):int(width='j) + +[1] * 2 +# CHECK-L: [1:int(width='l)]:list(elt=int(width='l)) * 2:int(width='m):list(elt=int(width='l)) + +1 / 2 +# CHECK-L: 1:int(width='n):int(width='o) / 2:int(width='p):int(width='o):int(width='o) + +1 + 1.0 +# CHECK-L: 1:int(width='q):float + 1.0:float:float diff --git a/lit-test/py2llvm/typing/error_coerce.py b/lit-test/py2llvm/typing/error_coerce.py new file mode 100644 index 000000000..acd90a48c --- /dev/null +++ b/lit-test/py2llvm/typing/error_coerce.py @@ -0,0 +1,35 @@ +# RUN: %python -m artiq.py2llvm.typing +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=int(width='a)) +# CHECK-L: ${LINE:+1}: note: int(width='b), which cannot be added to a list +[1] + 2 + +# CHECK-L: ${LINE:+1}: error: cannot unify list(elt=int(width='a)) with list(elt=float): int(width='a) 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 (int(width='a),) +# CHECK-L: ${LINE:+1}: note: int(width='b), which cannot be added to a tuple +(1,) + 2 + +# CHECK-L: ${LINE:+1}: error: py2llvm does not support passing tuples to '*' +(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=int(width='a)) +# CHECK-L: ${LINE:+1}: note: operand of type list(elt='b), which is not a valid repetition amount +[1] * [] + +# CHECK-L: ${LINE:+3}: error: cannot coerce list(elt='a) and NoneType to a common numeric type +# CHECK-L: ${LINE:+2}: note: expression of type list(elt='a) +# CHECK-L: ${LINE:+1}: note: expression of type NoneType +[] - None + +# CHECK-L: ${LINE:+2}: error: cannot coerce list(elt='a) to float +# CHECK-L: ${LINE:+1}: note: expression that required coercion to float +[] - 1.0 diff --git a/lit-test/py2llvm/typing/error_unify.py b/lit-test/py2llvm/typing/error_unify.py index d268837e1..3abef5b13 100644 --- a/lit-test/py2llvm/typing/error_unify.py +++ b/lit-test/py2llvm/typing/error_unify.py @@ -17,10 +17,10 @@ a = b # CHECK-L: note: an operand of type int(width='a) # 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 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 +# CHECK-L: ${LINE:+1}: error: expected '~' operand to be of integer type, not float ~1.0 # CHECK-L: ${LINE:+1}: error: type int(width='a) does not have an attribute 'x' From fe69c5b4658206f3d608030d33b7aa6786e133a2 Mon Sep 17 00:00:00 2001 From: whitequark Date: Sun, 14 Jun 2015 13:10:32 +0300 Subject: [PATCH 038/369] Implement BinOp coercion rules for AugAssign. --- artiq/py2llvm/typing.py | 129 +++++++++++++++--------- lit-test/py2llvm/typing/coerce.py | 3 + lit-test/py2llvm/typing/error_coerce.py | 8 ++ 3 files changed, 94 insertions(+), 46 deletions(-) diff --git a/artiq/py2llvm/typing.py b/artiq/py2llvm/typing.py index 0f87dcd17..ec4991eb7 100644 --- a/artiq/py2llvm/typing.py +++ b/artiq/py2llvm/typing.py @@ -461,7 +461,7 @@ class Inferencer(algorithm.Visitor): self.visit(node) return node - def _coerce_numeric(self, return_type, left, right): + def _coerce_numeric(self, left, right): # Implements the coercion protocol. # See https://docs.python.org/3/library/stdtypes.html#numeric-types-int-float-complex. if builtins.is_float(left.type) or builtins.is_float(right.type): @@ -474,7 +474,7 @@ class Inferencer(algorithm.Visitor): else: typ = builtins.TInt() elif types.is_var(left.type) or types.is_var(right.type): # not enough info yet - return left, right + return else: # conflicting types printer = types.TypePrinter() note1 = diagnostic.Diagnostic("note", @@ -489,15 +489,9 @@ class Inferencer(algorithm.Visitor): left.loc, [right.loc], [note1, note2]) self.engine.process(diag) - return left, right + return - # On 1st invocation, return_type is always a type variable. - # On further invocations, coerce will only ever refine the type, - # so this should never fail. - return_type.unify(typ) - - return self._coerce_one(typ, left, other_node=right), \ - self._coerce_one(typ, right, other_node=left) + return typ, typ, typ def _order_by_pred(self, pred, left, right): if pred(left.type): @@ -507,28 +501,26 @@ class Inferencer(algorithm.Visitor): else: assert False - def visit_BinOpT(self, node): - self.generic_visit(node) - if isinstance(node.op, (ast.BitAnd, ast.BitOr, ast.BitXor, + def _coerce_binop(self, op, left, right): + if isinstance(op, (ast.BitAnd, ast.BitOr, ast.BitXor, ast.LShift, ast.RShift)): # bitwise operators require integers - for operand in (node.left, node.right): + 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": node.op.loc.source(), + {"op": op.loc.source(), "type": types.TypePrinter().name(operand.type)}, - node.op.loc, [operand.loc]) + op.loc, [operand.loc]) self.engine.process(diag) return - node.left, node.right = \ - self._coerce_numeric(node.type, node.left, node.right) - elif isinstance(node.op, ast.Add): + return self._coerce_numeric(left, right) + elif isinstance(op, ast.Add): # add works on numbers and also collections - if builtins.is_collection(node.left.type) or builtins.is_collection(node.right.type): + if builtins.is_collection(left.type) or builtins.is_collection(right.type): collection, other = \ - self._order_by_pred(builtins.is_collection, node.left, node.right) + 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): @@ -548,32 +540,32 @@ class Inferencer(algorithm.Visitor): diag = diagnostic.Diagnostic("error", "expected every '+' operand to be a {kind} in this context", {"kind": kind}, - node.op.loc, [other.loc, collection.loc], + op.loc, [other.loc, collection.loc], [note1, note2]) self.engine.process(diag) return if types.is_tuple(collection.type): # should never fail - node.type.unify(types.TTuple(node.left.type.find().elts + - node.right.type.find().elts)) + return types.TTuple(left.type.find().elts + + right.type.find().elts), left.type, right.type elif builtins.is_list(collection.type): - self._unify(node.left.type, node.right.type, - node.left.loc, node.right.loc) - node.type.unify(node.left.type) # should never fail + self._unify(left.type, right.type, + left.loc, right.loc) + return left.type, left.type, right.type else: - node.left, node.right = \ - self._coerce_numeric(node.type, node.left, node.right) - elif isinstance(node.op, ast.Mult): + return self._coerce_numeric(left, right) + elif isinstance(op, ast.Mult): # mult works on numbers and also number & collection - if types.is_tuple(node.left.type) or types.is_tuple(node.right.type): - tuple_, other = self._order_by_pred(types.is_tuple, node.left, node.right) + 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", "py2llvm does not support passing tuples to '*'", {}, - node.op.loc, [tuple_.loc]) + op.loc, [tuple_.loc]) self.engine.process(diag) - elif builtins.is_list(node.left.type) or builtins.is_list(node.right.type): - list_, other = self._order_by_pred(builtins.is_list, node.left, node.right) + 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): printer = types.TypePrinter() note1 = diagnostic.Diagnostic("note", @@ -586,25 +578,33 @@ class Inferencer(algorithm.Visitor): other.loc) diag = diagnostic.Diagnostic("error", "expected '*' operands to be a list and an integer in this context", {}, - node.op.loc, [list_.loc, other.loc], + op.loc, [list_.loc, other.loc], [note1, note2]) self.engine.process(diag) return - node.type.unify(list_.type) + + return list_.type, left.type, right.type else: - node.left, node.right = \ - self._coerce_numeric(node.type, node.left, node.right) - elif isinstance(node.op, (ast.Div, ast.FloorDiv, ast.Mod, ast.Pow, ast.Sub)): + return self._coerce_numeric(left, right) + elif isinstance(op, (ast.Div, ast.FloorDiv, ast.Mod, ast.Pow, ast.Sub)): # numeric operators work on any kind of number - node.left, node.right = \ - self._coerce_numeric(node.type, node.left, node.right) + return self._coerce_numeric(left, right) else: # MatMult diag = diagnostic.Diagnostic("error", - "operator '{op}' is not supported", {"op": node.op.loc.source()}, - node.op.loc) + "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) + node.type.unify(return_type) # should never fail + def visit_Assign(self, node): self.generic_visit(node) if len(node.targets) > 1: @@ -616,8 +616,45 @@ class Inferencer(algorithm.Visitor): def visit_AugAssign(self, node): self.generic_visit(node) - self._unify(node.target.type, node.value.type, - node.target.loc, node.value.loc) + coerced = self._coerce_binop(node.op, node.target, node.value) + if coerced: + return_type, target_type, value_type = coerced + + 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(node.value.type)}, + node.value.loc) + diag = diagnostic.Diagnostic("error", + "expression of type {typea} has to be coerced to {typeb}, " + "which makes assignment invalid", + {"typea": printer.name(node.target.type), + "typeb": printer.name(target_type)}, + node.op.loc, [node.target.loc], [note]) + self.engine.process(diag) + return + + 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(node.value.type)}, + node.value.loc) + diag = diagnostic.Diagnostic("error", + "the result of this operation has type {typeb}, " + "which makes assignment to a slot of type {typea} invalid", + {"typea": printer.name(node.target.type), + "typeb": printer.name(return_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_For(self, node): old_in_loop, self.in_loop = self.in_loop, True diff --git a/lit-test/py2llvm/typing/coerce.py b/lit-test/py2llvm/typing/coerce.py index ac33002d9..786f3e81a 100644 --- a/lit-test/py2llvm/typing/coerce.py +++ b/lit-test/py2llvm/typing/coerce.py @@ -24,3 +24,6 @@ 1 + 1.0 # CHECK-L: 1:int(width='q):float + 1.0:float:float + +a = []; a += [1] +# CHECK-L: a:list(elt=int(width='r)) = []:list(elt=int(width='r)); a:list(elt=int(width='r)) += [1:int(width='r)]:list(elt=int(width='r)) diff --git a/lit-test/py2llvm/typing/error_coerce.py b/lit-test/py2llvm/typing/error_coerce.py index acd90a48c..ad8416577 100644 --- a/lit-test/py2llvm/typing/error_coerce.py +++ b/lit-test/py2llvm/typing/error_coerce.py @@ -33,3 +33,11 @@ # CHECK-L: ${LINE:+2}: error: cannot coerce list(elt='a) to float # CHECK-L: ${LINE:+1}: note: expression that required coercion to float [] - 1.0 + +# CHECK-L: ${LINE:+2}: error: expression of type int(width='a) has to be coerced to float, which makes assignment invalid +# 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 (int(width='a), float), which makes assignment to a slot of type (int(width='a),) invalid +# CHECK-L: ${LINE:+1}: note: expression of type (float,) +b = (1,); b += (1.0,) From 20b7a73b49c086873446d2f8d92e040976712964 Mon Sep 17 00:00:00 2001 From: whitequark Date: Sun, 14 Jun 2015 22:48:04 +0300 Subject: [PATCH 039/369] Add support for Compare. --- artiq/py2llvm/types.py | 7 +- artiq/py2llvm/typing.py | 106 ++++++++++++++++-------- lit-test/py2llvm/typing/coerce.py | 12 +++ lit-test/py2llvm/typing/error_coerce.py | 8 +- 4 files changed, 87 insertions(+), 46 deletions(-) diff --git a/artiq/py2llvm/types.py b/artiq/py2llvm/types.py index 97aff58ca..19ad56d48 100644 --- a/artiq/py2llvm/types.py +++ b/artiq/py2llvm/types.py @@ -163,13 +163,14 @@ class TValue(Type): def is_var(typ): return isinstance(typ.find(), TVar) -def is_mono(typ, name, **params): +def is_mono(typ, name=None, **params): typ = typ.find() params_match = True for param in params: - params_match = params_match and typ.params[param] == params[param] + params_match = params_match and \ + typ.params[param].find() == params[param].find() return isinstance(typ, TMono) and \ - typ.name == name and params_match + (name is None or (typ.name == name and params_match)) def is_tuple(typ, elts=None): typ = typ.find() diff --git a/artiq/py2llvm/typing.py b/artiq/py2llvm/typing.py index ec4991eb7..875920df3 100644 --- a/artiq/py2llvm/typing.py +++ b/artiq/py2llvm/typing.py @@ -283,7 +283,6 @@ class ASTTypedRewriter(algorithm.Transformer): # expr visit_Call = visit_unsupported - visit_Compare = visit_unsupported visit_Dict = visit_unsupported visit_DictComp = visit_unsupported visit_Ellipsis = visit_unsupported @@ -393,11 +392,14 @@ class Inferencer(algorithm.Visitor): node.attr_loc, [node.value.loc]) self.engine.process(diag) + def _unify_collection(self, element, collection): + # TODO: support more than just lists + self._unify(builtins.TList(element.type), collection.type, + element.loc, collection.loc) + def visit_SubscriptT(self, node): self.generic_visit(node) - # TODO: support more than just lists - self._unify(builtins.TList(node.type), node.value.type, - node.loc, node.value.loc) + self._unify_collection(element=node, collection=node.value) def visit_IfExpT(self, node): self.generic_visit(node) @@ -455,43 +457,39 @@ class Inferencer(algorithm.Visitor): 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.type, node.other_expr = typ, other_node else: node = asttyped.CoerceT(type=typ, expr=coerced_node, other_expr=other_node, loc=coerced_node.loc) - self.visit(node) - return node + self.visit(node) + return node - def _coerce_numeric(self, left, right): - # Implements the coercion protocol. + def _coerce_numeric(self, nodes, map_return=lambda typ: typ): # See https://docs.python.org/3/library/stdtypes.html#numeric-types-int-float-complex. - if builtins.is_float(left.type) or builtins.is_float(right.type): - typ = builtins.TFloat() - elif builtins.is_int(left.type) or builtins.is_int(right.type): - left_width, right_width = \ - builtins.get_int_width(left.type), builtins.get_int_width(left.type) - if left_width and right_width: - typ = builtins.TInt(types.TValue(max(left_width, right_width))) - else: - typ = builtins.TInt() - elif types.is_var(left.type) or types.is_var(right.type): # not enough info yet + node_types = [node.type for node in nodes] + if any(map(types.is_var, node_types)): # not enough info yet return - else: # conflicting types - printer = types.TypePrinter() - note1 = diagnostic.Diagnostic("note", - "expression of type {typea}", {"typea": printer.name(left.type)}, - left.loc) - note2 = diagnostic.Diagnostic("note", - "expression of type {typeb}", {"typeb": printer.name(right.type)}, - right.loc) + 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 {typea} and {typeb} to a common numeric type", - {"typea": printer.name(left.type), "typeb": printer.name(right.type)}, - left.loc, [right.loc], - [note1, note2]) + "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 = 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 typ, typ, typ + return map_return(typ) def _order_by_pred(self, pred, left, right): if pred(left.type): @@ -503,7 +501,7 @@ class Inferencer(algorithm.Visitor): def _coerce_binop(self, op, left, right): if isinstance(op, (ast.BitAnd, ast.BitOr, ast.BitXor, - ast.LShift, ast.RShift)): + 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): @@ -515,7 +513,7 @@ class Inferencer(algorithm.Visitor): self.engine.process(diag) return - return self._coerce_numeric(left, right) + 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): @@ -554,7 +552,7 @@ class Inferencer(algorithm.Visitor): left.loc, right.loc) return left.type, left.type, right.type else: - return self._coerce_numeric(left, right) + 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): @@ -585,10 +583,10 @@ class Inferencer(algorithm.Visitor): return list_.type, left.type, right.type else: - return self._coerce_numeric(left, right) + return self._coerce_numeric((left, right), lambda typ: (typ, typ, typ)) elif isinstance(op, (ast.Div, ast.FloorDiv, ast.Mod, ast.Pow, ast.Sub)): # numeric operators work on any kind of number - return self._coerce_numeric(left, right) + return self._coerce_numeric((left, right), lambda typ: (typ, typ, typ)) else: # MatMult diag = diagnostic.Diagnostic("error", "operator '{op}' is not supported", {"op": op.loc.source()}, @@ -605,6 +603,42 @@ class Inferencer(algorithm.Visitor): node.right = self._coerce_one(right_type, node.right, other_node=node.left) node.type.unify(return_type) # should never fail + 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_collection(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) + else: + 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)) + print(typ, other_node) + node.left, *node.comparators = \ + [self._coerce_one(typ, operand, other_node) for operand in operands] + node.type.unify(builtins.TBool()) + def visit_Assign(self, node): self.generic_visit(node) if len(node.targets) > 1: diff --git a/lit-test/py2llvm/typing/coerce.py b/lit-test/py2llvm/typing/coerce.py index 786f3e81a..34df20e7e 100644 --- a/lit-test/py2llvm/typing/coerce.py +++ b/lit-test/py2llvm/typing/coerce.py @@ -27,3 +27,15 @@ a = []; a += [1] # CHECK-L: a:list(elt=int(width='r)) = []:list(elt=int(width='r)); a:list(elt=int(width='r)) += [1:int(width='r)]:list(elt=int(width='r)) + +[] is [1] +# CHECK-L: []:list(elt=int(width='s)) is [1:int(width='s)]:list(elt=int(width='s)):bool + +1 in [1] +# CHECK-L: 1:int(width='t) in [1:int(width='t)]:list(elt=int(width='t)):bool + +[] < [1] +# CHECK-L: []:list(elt=int(width='u)) < [1:int(width='u)]:list(elt=int(width='u)):bool + +1.0 < 1 +# CHECK-L: 1.0:float < 1:int(width='v):float:bool diff --git a/lit-test/py2llvm/typing/error_coerce.py b/lit-test/py2llvm/typing/error_coerce.py index ad8416577..0ac65724f 100644 --- a/lit-test/py2llvm/typing/error_coerce.py +++ b/lit-test/py2llvm/typing/error_coerce.py @@ -25,13 +25,7 @@ # CHECK-L: ${LINE:+1}: note: operand of type list(elt='b), which is not a valid repetition amount [1] * [] -# CHECK-L: ${LINE:+3}: error: cannot coerce list(elt='a) and NoneType to a common numeric type -# CHECK-L: ${LINE:+2}: note: expression of type list(elt='a) -# CHECK-L: ${LINE:+1}: note: expression of type NoneType -[] - None - -# CHECK-L: ${LINE:+2}: error: cannot coerce list(elt='a) to float -# CHECK-L: ${LINE:+1}: note: expression that required coercion to float +# CHECK-L: ${LINE:+1}: error: cannot coerce list(elt='a) to a numeric type [] - 1.0 # CHECK-L: ${LINE:+2}: error: expression of type int(width='a) has to be coerced to float, which makes assignment invalid From cd22b8178c048e9a8a270bdfcd297eb625607689 Mon Sep 17 00:00:00 2001 From: whitequark Date: Sun, 14 Jun 2015 23:02:28 +0300 Subject: [PATCH 040/369] Add support for Raise. --- artiq/py2llvm/typing.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/artiq/py2llvm/typing.py b/artiq/py2llvm/typing.py index 875920df3..a52041275 100644 --- a/artiq/py2llvm/typing.py +++ b/artiq/py2llvm/typing.py @@ -273,6 +273,15 @@ class ASTTypedRewriter(algorithm.Transformer): if_loc=node.if_loc, else_loc=node.else_loc, loc=node.loc) return self.visit(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 + # Unsupported visitors # def visit_unsupported(self, node): @@ -302,7 +311,6 @@ class ASTTypedRewriter(algorithm.Transformer): visit_Delete = visit_unsupported visit_Import = visit_unsupported visit_ImportFrom = visit_unsupported - visit_Raise = visit_unsupported visit_Try = visit_unsupported visit_With = visit_unsupported From 77adf2f6b557fb866bda54eeef3aae6002be5507 Mon Sep 17 00:00:00 2001 From: whitequark Date: Sun, 14 Jun 2015 23:13:41 +0300 Subject: [PATCH 041/369] Add support for With. --- artiq/py2llvm/typing.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/artiq/py2llvm/typing.py b/artiq/py2llvm/typing.py index a52041275..5e2c498fc 100644 --- a/artiq/py2llvm/typing.py +++ b/artiq/py2llvm/typing.py @@ -312,7 +312,6 @@ class ASTTypedRewriter(algorithm.Transformer): visit_Import = visit_unsupported visit_ImportFrom = visit_unsupported visit_Try = visit_unsupported - visit_With = visit_unsupported class Inferencer(algorithm.Visitor): @@ -725,6 +724,15 @@ class Inferencer(algorithm.Visitor): node.keyword_loc) self.engine.process(diag) + def visit_withitem(self, node): + self.generic_visit(node) + if True: # none are supported yet + diag = diagnostic.Diagnostic("error", + "value of type {type} cannot act as a context manager", + {"type": types.TypePrinter().name(node.context_expr.type)}, + node.context_expr.loc) + self.engine.process(diag) + def visit_FunctionDefT(self, node): old_function, self.function = self.function, node old_in_loop, self.in_loop = self.in_loop, False From d27bb3168d94c79081bd2aeaadc67f07253bbc5f Mon Sep 17 00:00:00 2001 From: whitequark Date: Mon, 15 Jun 2015 08:40:37 +0300 Subject: [PATCH 042/369] Add support for ListComp. --- artiq/py2llvm/builtins.py | 2 +- artiq/py2llvm/typing.py | 25 ++++++++++++++++++++++++- lit-test/py2llvm/typing/unify.py | 3 +++ 3 files changed, 28 insertions(+), 2 deletions(-) diff --git a/artiq/py2llvm/builtins.py b/artiq/py2llvm/builtins.py index 569b85cb2..48621ce10 100644 --- a/artiq/py2llvm/builtins.py +++ b/artiq/py2llvm/builtins.py @@ -44,7 +44,7 @@ def is_int(typ, width=None): def get_int_width(typ): if is_int(typ): - return types.get_value(typ["width"]) + return types.get_value(typ.find()["width"]) def is_float(typ): return types.is_mono(typ, "float") diff --git a/artiq/py2llvm/typing.py b/artiq/py2llvm/typing.py index 5e2c498fc..5d2b140a9 100644 --- a/artiq/py2llvm/typing.py +++ b/artiq/py2llvm/typing.py @@ -273,6 +273,22 @@ class ASTTypedRewriter(algorithm.Transformer): 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_Raise(self, node): node = self.generic_visit(node) if node.cause: @@ -297,7 +313,6 @@ class ASTTypedRewriter(algorithm.Transformer): visit_Ellipsis = visit_unsupported visit_GeneratorExp = visit_unsupported visit_Lambda = visit_unsupported - visit_ListComp = visit_unsupported visit_Set = visit_unsupported visit_SetComp = visit_unsupported visit_Str = visit_unsupported @@ -646,6 +661,14 @@ class Inferencer(algorithm.Visitor): [self._coerce_one(typ, operand, other_node) for operand in operands] node.type.unify(builtins.TBool()) + def visit_ListCompT(self, node): + self.generic_visit(node) + node.type.unify(builtins.TList(node.elt.type)) # should never fail + + def visit_comprehension(self, node): + self.generic_visit(node) + self._unify_collection(element=node.target, collection=node.iter) + def visit_Assign(self, node): self.generic_visit(node) if len(node.targets) > 1: diff --git a/lit-test/py2llvm/typing/unify.py b/lit-test/py2llvm/typing/unify.py index 64dfb20b7..94207cc1f 100644 --- a/lit-test/py2llvm/typing/unify.py +++ b/lit-test/py2llvm/typing/unify.py @@ -47,3 +47,6 @@ True and False not 1 # CHECK-L: 1:int(width='i):bool + +[x for x in [1]] +# CHECK-L: [x:int(width='j) for x:int(width='j) in [1:int(width='j)]:list(elt=int(width='j))]:list(elt=int(width='j)) From dbfdbc3c22fffced22b20d5057f481cf23f9fabb Mon Sep 17 00:00:00 2001 From: whitequark Date: Mon, 15 Jun 2015 09:05:24 +0300 Subject: [PATCH 043/369] Add check for duplicate parameter names. --- artiq/py2llvm/typing.py | 6 ++++++ .../typing/{error_nonlocal_global.py => error_locals.py} | 6 +++++- 2 files changed, 11 insertions(+), 1 deletion(-) rename lit-test/py2llvm/typing/{error_nonlocal_global.py => error_locals.py} (89%) diff --git a/artiq/py2llvm/typing.py b/artiq/py2llvm/typing.py index 5d2b140a9..29981389a 100644 --- a/artiq/py2llvm/typing.py +++ b/artiq/py2llvm/typing.py @@ -71,6 +71,12 @@ class LocalExtractor(algorithm.Visitor): 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) diff --git a/lit-test/py2llvm/typing/error_nonlocal_global.py b/lit-test/py2llvm/typing/error_locals.py similarity index 89% rename from lit-test/py2llvm/typing/error_nonlocal_global.py rename to lit-test/py2llvm/typing/error_locals.py index 1242f56db..7d07d699b 100644 --- a/lit-test/py2llvm/typing/error_nonlocal_global.py +++ b/lit-test/py2llvm/typing/error_locals.py @@ -20,6 +20,10 @@ def d(x): # CHECK-L: ${LINE:+1}: error: name 'x' cannot be a parameter and global simultaneously global x -def d(x): +def e(x): # CHECK-L: ${LINE:+1}: error: name 'x' cannot be a parameter and nonlocal simultaneously nonlocal x + +# CHECK-L: ${LINE:+1}: error: duplicate parameter 'x' +def f(x, x): + pass From 20e0e69358481c11e92ed9ae7ca3c49c5c85b6a5 Mon Sep 17 00:00:00 2001 From: whitequark Date: Mon, 15 Jun 2015 11:30:50 +0300 Subject: [PATCH 044/369] Add support for function types and LambdaT. Also fix scoping of Nonlocal. --- artiq/py2llvm/asttyped.py | 2 +- artiq/py2llvm/types.py | 64 +++++++++++- artiq/py2llvm/typing.py | 127 +++++++++++++++++++----- lit-test/py2llvm/typing/error_locals.py | 36 ++++--- lit-test/py2llvm/typing/scoping.py | 8 ++ lit-test/py2llvm/typing/unify.py | 4 + 6 files changed, 195 insertions(+), 46 deletions(-) create mode 100644 lit-test/py2llvm/typing/scoping.py diff --git a/artiq/py2llvm/asttyped.py b/artiq/py2llvm/asttyped.py index e96edd641..a9762ac4e 100644 --- a/artiq/py2llvm/asttyped.py +++ b/artiq/py2llvm/asttyped.py @@ -29,7 +29,7 @@ class argT(ast.arg, commontyped): class ClassDefT(ast.ClassDef, scoped): pass class FunctionDefT(ast.FunctionDef, scoped): - pass + _types = ("signature_type",) class ModuleT(ast.Module, scoped): pass diff --git a/artiq/py2llvm/types.py b/artiq/py2llvm/types.py index 19ad56d48..84823ddaa 100644 --- a/artiq/py2llvm/types.py +++ b/artiq/py2llvm/types.py @@ -58,7 +58,7 @@ class TVar(Type): def __repr__(self): if self.parent is self: - return "TVar(%d)" % id(self) + return "" % id(self) else: return repr(self.find()) @@ -88,7 +88,7 @@ class TMono(Type): raise UnificationError(self, other) def __repr__(self): - return "TMono(%s, %s)" % (repr(self.name), repr(self.params)) + return "py2llvm.types.TMono(%s, %s)" % (repr(self.name), repr(self.params)) def __getitem__(self, param): return self.params[param] @@ -102,7 +102,11 @@ class TMono(Type): return not (self == other) class TTuple(Type): - """A tuple type.""" + """ + A tuple type. + + :ivar elts: (list of :class:`Type`) elements + """ attributes = {} @@ -122,7 +126,7 @@ class TTuple(Type): raise UnificationError(self, other) def __repr__(self): - return "TTuple(%s)" % (", ".join(map(repr, self.elts))) + return "py2llvm.types.TTuple(%s)" % repr(self.elts) def __eq__(self, other): return isinstance(other, TTuple) and \ @@ -131,6 +135,51 @@ class TTuple(Type): def __ne__(self, other): return not (self == other) +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 + """ + + attributes = {} + + def __init__(self, args, optargs, ret): + self.args, self.optargs, self.ret = args, optargs, ret + + def find(self): + return self + + def unify(self, other): + if isinstance(other, TFunction) and \ + self.args.keys() == other.args.keys() and \ + self.optargs.keys() == other.optargs.keys(): + for selfarg, otherarg in zip(self.args.values() + self.optargs.values(), + other.args.values() + other.optargs.values()): + selfarg.unify(otherarg) + self.ret.unify(other.ret) + elif isinstance(other, TVar): + other.unify(self) + else: + raise UnificationError(self, other) + + def __repr__(self): + return "py2llvm.types.TFunction(%s, %s, %s)" % \ + (repr(self.args), repr(self.optargs), repr(self.ret)) + + def __eq__(self, other): + return isinstance(other, TFunction) and \ + self.args == other.args and \ + self.optargs == other.optargs + + def __ne__(self, other): + return not (self == other) + class TValue(Type): """ A type-level value (such as the integer denoting width of @@ -150,7 +199,7 @@ class TValue(Type): raise UnificationError(self, other) def __repr__(self): - return "TValue(%s)" % repr(self.value) + return "py2llvm.types.TValue(%s)" % repr(self.value) def __eq__(self, other): return isinstance(other, TValue) and \ @@ -216,6 +265,11 @@ class TypePrinter(object): return "(%s,)" % self.name(typ.elts[0]) else: return "(%s)" % ", ".join(list(map(self.name, typ.elts))) + elif isinstance(typ, TFunction): + args = [] + args += [ "%s:%s" % (arg, self.name(typ.args[arg])) for arg in typ.args] + args += ["?%s:%s" % (arg, self.name(typ.optargs[arg])) for arg in typ.optargs] + return "(%s)->%s" % (", ".join(args), self.name(typ.ret)) elif isinstance(typ, TValue): return repr(typ.value) else: diff --git a/artiq/py2llvm/typing.py b/artiq/py2llvm/typing.py index 29981389a..6b10cc795 100644 --- a/artiq/py2llvm/typing.py +++ b/artiq/py2llvm/typing.py @@ -1,4 +1,5 @@ from pythonparser import source, ast, algorithm, diagnostic, parse_buffer +from collections import OrderedDict from . import asttyped, types, builtins # This visitor will be called for every node with a scope, @@ -23,6 +24,9 @@ class LocalExtractor(algorithm.Visitor): # parameters can't be declared as global or nonlocal self.params = set() + if len(self.env_stack) == 0: + self.env_stack.append(self.typing_env) + def visit_in_assign(self, node): try: self.in_assign = True @@ -58,14 +62,19 @@ class LocalExtractor(algorithm.Visitor): self.in_root = True self.generic_visit(node) - visit_ClassDef = visit_root # don't look at inner scopes - visit_FunctionDef = visit_root + visit_Module = visit_root # don't look at inner scopes + visit_ClassDef = visit_root visit_Lambda = visit_root visit_DictComp = visit_root visit_ListComp = visit_root visit_SetComp = visit_root visit_GeneratorExp = visit_root + def visit_FunctionDef(self, node): + if self.in_root: + self._assignable(node.name) + self.visit_root(node) + def _assignable(self, name): if name not in self.typing_env and name not in self.nonlocal_: self.typing_env[name] = types.TVar() @@ -103,7 +112,10 @@ class LocalExtractor(algorithm.Visitor): 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) + self._assignable(name) + self.env_stack[0][name] = self.typing_env[name] def visit_Nonlocal(self, node): for name, loc in zip(node.names, node.name_locs): @@ -111,8 +123,9 @@ class LocalExtractor(algorithm.Visitor): self._check_not_in(name, self.params, "a parameter", "nonlocal", loc): continue + # nonlocal does not search global scope found = False - for outer_env in reversed(self.env_stack): + for outer_env in reversed(self.env_stack[1:]): if name in outer_env: found = True break @@ -164,12 +177,7 @@ class ASTTypedRewriter(algorithm.Transformer): node = asttyped.ModuleT( typing_env=extractor.typing_env, globals_in_scope=extractor.global_, body=node.body, loc=node.loc) - - try: - self.env_stack.append(node.typing_env) - return self.generic_visit(node) - finally: - self.env_stack.pop() + return self.generic_visit(node) def visit_FunctionDef(self, node): extractor = LocalExtractor(env_stack=self.env_stack, engine=self.engine) @@ -177,7 +185,7 @@ class ASTTypedRewriter(algorithm.Transformer): node = asttyped.FunctionDefT( typing_env=extractor.typing_env, globals_in_scope=extractor.global_, - return_type=types.TVar(), + signature_type=self._find_name(node.name, node.name_loc), 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, @@ -295,6 +303,22 @@ class ASTTypedRewriter(algorithm.Transformer): finally: self.env_stack.pop() + 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_Raise(self, node): node = self.generic_visit(node) if node.cause: @@ -318,7 +342,6 @@ class ASTTypedRewriter(algorithm.Transformer): visit_DictComp = visit_unsupported visit_Ellipsis = visit_unsupported visit_GeneratorExp = visit_unsupported - visit_Lambda = visit_unsupported visit_Set = visit_unsupported visit_SetComp = visit_unsupported visit_Str = visit_unsupported @@ -363,12 +386,14 @@ class Inferencer(algorithm.Visitor): diagnostic.Diagnostic("note", "expression of type {typea}", {"typea": printer.name(typea)}, - loca), - diagnostic.Diagnostic("note", - "expression of type {typeb}", - {"typeb": printer.name(typeb)}, - locb) + 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(): @@ -412,7 +437,8 @@ class Inferencer(algorithm.Visitor): if not types.is_var(object_type): if node.attr in object_type.attributes: # assumes no free type variables in .attributes - node.type.unify(object_type.attributes[node.attr]) # should never fail + self._unify(node.type, object_type.attributes[node.attr], + node.loc, None) else: diag = diagnostic.Diagnostic("error", "type {type} does not have an attribute '{attr}'", @@ -433,7 +459,8 @@ class Inferencer(algorithm.Visitor): self.generic_visit(node) self._unify(node.body.type, node.orelse.type, node.body.loc, node.orelse.loc) - node.type.unify(node.body.type) # should never fail + self._unify(node.type, node.body.type, + node.loc, None) def visit_BoolOpT(self, node): self.generic_visit(node) @@ -445,10 +472,12 @@ class Inferencer(algorithm.Visitor): self.generic_visit(node) operand_type = node.operand.type.find() if isinstance(node.op, ast.Not): - node.type.unify(builtins.TBool()) # should never fail + self._unify(node.type, builtins.TBool(), + node.loc, None) elif isinstance(node.op, ast.Invert): if builtins.is_int(operand_type): - node.type.unify(operand_type) # should never fail + 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}", @@ -457,7 +486,8 @@ class Inferencer(algorithm.Visitor): self.engine.process(diag) else: # UAdd, USub if builtins.is_numeric(operand_type): - node.type.unify(operand_type) # should never fail + self._unify(node.type, operand_type, + node.loc, None) elif not types.is_var(operand_type): diag = diagnostic.Diagnostic("error", "expected unary '{op}' operand to be of numeric type, not {type}", @@ -572,7 +602,6 @@ class Inferencer(algorithm.Visitor): return if types.is_tuple(collection.type): - # should never fail return types.TTuple(left.type.find().elts + right.type.find().elts), left.type, right.type elif builtins.is_list(collection.type): @@ -629,7 +658,8 @@ class Inferencer(algorithm.Visitor): 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) - node.type.unify(return_type) # should never fail + self._unify(node.type, return_type, + node.loc, None) def visit_CompareT(self, node): self.generic_visit(node) @@ -665,16 +695,25 @@ class Inferencer(algorithm.Visitor): print(typ, other_node) node.left, *node.comparators = \ [self._coerce_one(typ, operand, other_node) for operand in operands] - node.type.unify(builtins.TBool()) + self._unify(node.type, builtins.TBool(), + node.loc, None) def visit_ListCompT(self, node): self.generic_visit(node) - node.type.unify(builtins.TList(node.elt.type)) # should never fail + self._unify(node.type, builtins.TList(node.elt.type), + node.loc, None) def visit_comprehension(self, node): self.generic_visit(node) self._unify_collection(element=node.target, collection=node.iter) + 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) if len(node.targets) > 1: @@ -762,6 +801,32 @@ class Inferencer(algorithm.Visitor): node.context_expr.loc) self.engine.process(diag) + 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.defaults):]), + ret) + + def visit_arguments(self, node): + self.generic_visit(node) + for arg, default in zip(node.args[len(node.defaults):], node.defaults): + self._unify(arg.type, default.type, + arg.loc, default.loc) + def visit_FunctionDefT(self, node): old_function, self.function = self.function, node old_in_loop, self.in_loop = self.in_loop, False @@ -769,6 +834,18 @@ class Inferencer(algorithm.Visitor): self.function = old_function self.in_loop = old_in_loop + 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) + 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) + def visit_Return(self, node): if not self.function: diag = diagnostic.Diagnostic("error", diff --git a/lit-test/py2llvm/typing/error_locals.py b/lit-test/py2llvm/typing/error_locals.py index 7d07d699b..836029b55 100644 --- a/lit-test/py2llvm/typing/error_locals.py +++ b/lit-test/py2llvm/typing/error_locals.py @@ -1,29 +1,35 @@ # RUN: %python -m artiq.py2llvm.typing +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 -x = 1 -def b(): - nonlocal x - # CHECK-L: ${LINE:+1}: error: name 'x' cannot be nonlocal and global simultaneously - global 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 x - # CHECK-L: ${LINE:+1}: error: name 'x' cannot be global and nonlocal simultaneously - nonlocal x + def c(): + global y + # CHECK-L: ${LINE:+1}: error: name 'y' cannot be global and nonlocal simultaneously + nonlocal y -def d(x): - # CHECK-L: ${LINE:+1}: error: name 'x' cannot be a parameter and global simultaneously - global x + def d(y): + # CHECK-L: ${LINE:+1}: error: name 'y' cannot be a parameter and global simultaneously + global y -def e(x): - # CHECK-L: ${LINE:+1}: error: name 'x' cannot be a parameter and nonlocal simultaneously - nonlocal x + 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/lit-test/py2llvm/typing/scoping.py b/lit-test/py2llvm/typing/scoping.py new file mode 100644 index 000000000..96eefeb14 --- /dev/null +++ b/lit-test/py2llvm/typing/scoping.py @@ -0,0 +1,8 @@ +# RUN: %python -m artiq.py2llvm.typing %s >%t +# RUN: OutputCheck %s --file-to-check=%t + +def f(): + global x + x = 1 +# CHECK-L: [x:int(width='a)] +[x] diff --git a/lit-test/py2llvm/typing/unify.py b/lit-test/py2llvm/typing/unify.py index 94207cc1f..ba441527f 100644 --- a/lit-test/py2llvm/typing/unify.py +++ b/lit-test/py2llvm/typing/unify.py @@ -50,3 +50,7 @@ not 1 [x for x in [1]] # CHECK-L: [x:int(width='j) for x:int(width='j) in [1:int(width='j)]:list(elt=int(width='j))]:list(elt=int(width='j)) + +lambda x, y=1: x +# CHECK-L: lambda x:'a, y:int(width='b)=1:int(width='b): x:'a:(x:'a, ?y:int(width='b))->'a + From a3789868f22ddad928752772f34b86c5c7104394 Mon Sep 17 00:00:00 2001 From: whitequark Date: Mon, 15 Jun 2015 15:59:36 +0300 Subject: [PATCH 045/369] More friendly artiq.py2llvm.typing testbench. --- artiq/py2llvm/typing.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/artiq/py2llvm/typing.py b/artiq/py2llvm/typing.py index 6b10cc795..c01363f3e 100644 --- a/artiq/py2llvm/typing.py +++ b/artiq/py2llvm/typing.py @@ -916,13 +916,14 @@ def main(): else: def process_diagnostic(diag): print("\n".join(diag.render())) - if diag.level == 'fatal': + if diag.level in ('fatal', 'error'): exit(1) engine = diagnostic.Engine() engine.process = process_diagnostic - buf = source.Buffer("".join(fileinput.input()), os.path.basename(fileinput.filename())) + buf = source.Buffer("".join(fileinput.input()).expandtabs(), + os.path.basename(fileinput.filename())) parsed, comments = parse_buffer(buf, engine=engine) typed = ASTTypedRewriter(engine=engine).visit(parsed) Inferencer(engine=engine).visit(typed) From 7a00a4a47f2d69815a1928e9481724f5ea0182de Mon Sep 17 00:00:00 2001 From: whitequark Date: Mon, 15 Jun 2015 16:00:19 +0300 Subject: [PATCH 046/369] Fix typo in a test. --- lit-test/py2llvm/typing/unify.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lit-test/py2llvm/typing/unify.py b/lit-test/py2llvm/typing/unify.py index ba441527f..f41fb10d5 100644 --- a/lit-test/py2llvm/typing/unify.py +++ b/lit-test/py2llvm/typing/unify.py @@ -52,5 +52,5 @@ not 1 # CHECK-L: [x:int(width='j) for x:int(width='j) in [1:int(width='j)]:list(elt=int(width='j))]:list(elt=int(width='j)) lambda x, y=1: x -# CHECK-L: lambda x:'a, y:int(width='b)=1:int(width='b): x:'a:(x:'a, ?y:int(width='b))->'a +# CHECK-L: lambda x:'k, y:int(width='l)=1:int(width='l): x:'k:(x:'k, ?y:int(width='l))->'k From 8c5e58f83cbd0702462611ab74c11e8faa0cdc3b Mon Sep 17 00:00:00 2001 From: whitequark Date: Mon, 15 Jun 2015 16:55:13 +0300 Subject: [PATCH 047/369] Implement Call. --- artiq/py2llvm/types.py | 6 ++ artiq/py2llvm/typing.py | 85 ++++++++++++++++++++++++++- lit-test/py2llvm/typing/error_call.py | 20 +++++++ lit-test/py2llvm/typing/gcd.py | 12 ++++ 4 files changed, 121 insertions(+), 2 deletions(-) create mode 100644 lit-test/py2llvm/typing/error_call.py create mode 100644 lit-test/py2llvm/typing/gcd.py diff --git a/artiq/py2llvm/types.py b/artiq/py2llvm/types.py index 84823ddaa..ee5ec0033 100644 --- a/artiq/py2llvm/types.py +++ b/artiq/py2llvm/types.py @@ -152,6 +152,9 @@ class TFunction(Type): def __init__(self, args, optargs, ret): self.args, self.optargs, self.ret = args, optargs, ret + def arity(self): + return len(self.args) + len(self.optargs) + def find(self): return self @@ -229,6 +232,9 @@ def is_tuple(typ, elts=None): else: return isinstance(typ, TTuple) +def is_function(typ): + return isinstance(typ.find(), TFunction) + def get_value(typ): typ = typ.find() if isinstance(typ, TVar): diff --git a/artiq/py2llvm/typing.py b/artiq/py2llvm/typing.py index c01363f3e..cb1009050 100644 --- a/artiq/py2llvm/typing.py +++ b/artiq/py2llvm/typing.py @@ -303,6 +303,15 @@ class ASTTypedRewriter(algorithm.Transformer): finally: self.env_stack.pop() + def visit_Call(self, node): + node = self.generic_visit(node) + node = asttyped.CallT(type=types.TVar(), + 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) @@ -337,7 +346,6 @@ class ASTTypedRewriter(algorithm.Transformer): self.engine.process(diag) # expr - visit_Call = visit_unsupported visit_Dict = visit_unsupported visit_DictComp = visit_unsupported visit_Ellipsis = visit_unsupported @@ -707,6 +715,79 @@ class Inferencer(algorithm.Visitor): self.generic_visit(node) self._unify_collection(element=node.target, collection=node.iter) + 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 + + if types.is_var(node.func.type): + return # not enough info yet + elif not types.is_function(node.func.type): + diag = diagnostic.Diagnostic("error", + "cannot call this expression of type {type}", + {"type": types.TypePrinter().name(node.func.type)}, + node.func.loc, []) + self.engine.process(diag) + return + + typ = node.func.type.find() + passed_args = set() + + 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 + + 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.add(formalname) + + for keyword in node.keywords: + if keyword.arg in passed_args: + diag = diagnostic.Diagnostic("error", + "the argument '{name}' is already passed", + {"name": keyword.arg}, + keyword.arg_loc) + 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) + passed_args.add(keyword.arg) + + 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 + def visit_LambdaT(self, node): self.generic_visit(node) signature_type = self._type_from_arguments(node.args, node.body.type) @@ -818,7 +899,7 @@ class Inferencer(algorithm.Visitor): return OrderedDict(args) return types.TFunction(extract_args(node.args[:len(node.args) - len(node.defaults)]), - extract_args(node.args[len(node.defaults):]), + extract_args(node.args[len(node.args) - len(node.defaults):]), ret) def visit_arguments(self, node): diff --git a/lit-test/py2llvm/typing/error_call.py b/lit-test/py2llvm/typing/error_call.py new file mode 100644 index 000000000..2d95078ac --- /dev/null +++ b/lit-test/py2llvm/typing/error_call.py @@ -0,0 +1,20 @@ +# RUN: %python -m artiq.py2llvm.typing +diag %s >%t +# RUN: OutputCheck %s --file-to-check=%t + +# CHECK-L: ${LINE:+1}: error: cannot call this expression of type 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' is already passed +f(1, x=1) + +# CHECK-L: ${LINE:+1}: error: mandatory argument 'x' is not passed +f() diff --git a/lit-test/py2llvm/typing/gcd.py b/lit-test/py2llvm/typing/gcd.py new file mode 100644 index 000000000..7433443be --- /dev/null +++ b/lit-test/py2llvm/typing/gcd.py @@ -0,0 +1,12 @@ +# RUN: %python -m artiq.py2llvm.typing %s >%t + +def _gcd(a, b): + if a < 0: + a = -a + while a: + c = a + a = b % a + b = c + return b + +_gcd(10, 25) From 3adb4150f4eaba72e54ba5a0f776b94e7352eacc Mon Sep 17 00:00:00 2001 From: whitequark Date: Mon, 15 Jun 2015 17:16:44 +0300 Subject: [PATCH 048/369] Fix type of Call. --- artiq/py2llvm/typing.py | 3 +++ lit-test/py2llvm/typing/gcd.py | 1 + 2 files changed, 4 insertions(+) diff --git a/artiq/py2llvm/typing.py b/artiq/py2llvm/typing.py index cb1009050..3ceae8eb3 100644 --- a/artiq/py2llvm/typing.py +++ b/artiq/py2llvm/typing.py @@ -788,6 +788,9 @@ class Inferencer(algorithm.Visitor): 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) diff --git a/lit-test/py2llvm/typing/gcd.py b/lit-test/py2llvm/typing/gcd.py index 7433443be..08c589535 100644 --- a/lit-test/py2llvm/typing/gcd.py +++ b/lit-test/py2llvm/typing/gcd.py @@ -9,4 +9,5 @@ def _gcd(a, b): b = c return b +# CHECK-L: _gcd:(a:int(width='a), b:int(width='a))->int(width='a)(10:int(width='a), 25:int(width='a)):int(width='a) _gcd(10, 25) From 8762729e803ed0dc4174a1d620e29f0d9faf8da8 Mon Sep 17 00:00:00 2001 From: whitequark Date: Wed, 24 Jun 2015 11:24:35 +0300 Subject: [PATCH 049/369] Add types.TBuiltin. --- artiq/py2llvm/types.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/artiq/py2llvm/types.py b/artiq/py2llvm/types.py index ee5ec0033..029744ef8 100644 --- a/artiq/py2llvm/types.py +++ b/artiq/py2llvm/types.py @@ -183,6 +183,33 @@ class TFunction(Type): def __ne__(self, other): return not (self == other) +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): + self.name = name + + def find(self): + return self + + def unify(self, other): + if self != other: + raise UnificationError(self, other) + + def __repr__(self): + return "py2llvm.types.TBuiltin(%s)" % repr(self.name) + + def __eq__(self, other): + return isinstance(other, TBuiltin) and \ + self.name == other.name + + def __ne__(self, other): + return not (self == other) + + class TValue(Type): """ A type-level value (such as the integer denoting width of @@ -276,6 +303,8 @@ class TypePrinter(object): args += [ "%s:%s" % (arg, self.name(typ.args[arg])) for arg in typ.args] args += ["?%s:%s" % (arg, self.name(typ.optargs[arg])) for arg in typ.optargs] return "(%s)->%s" % (", ".join(args), self.name(typ.ret)) + elif isinstance(typ, TBuiltin): + return "" % typ.name elif isinstance(typ, TValue): return repr(typ.value) else: From 710a04cbee5819ab3a4789786e4d196f3c6313f6 Mon Sep 17 00:00:00 2001 From: whitequark Date: Wed, 24 Jun 2015 11:28:24 +0300 Subject: [PATCH 050/369] Add builtin definitions for len(), round(), range() and syscall(). --- artiq/py2llvm/builtins.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/artiq/py2llvm/builtins.py b/artiq/py2llvm/builtins.py index 48621ce10..daa94c70a 100644 --- a/artiq/py2llvm/builtins.py +++ b/artiq/py2llvm/builtins.py @@ -5,6 +5,8 @@ types, such as int or float. from . import types +# Types + class TNone(types.TMono): def __init__(self): super().__init__("NoneType") @@ -29,6 +31,19 @@ class TList(types.TMono): elt = types.TVar() super().__init__("list", {"elt": elt}) +def fn_len(): + return types.TBuiltin("len") + +def fn_round(): + return types.TBuiltin("round") + +def fn_range(): + return types.TBuiltin("range") + +def fn_syscall(): + return types.TBuiltin("syscall") + +# Accessors def is_none(typ): return types.is_mono(typ, "NoneType") From 4d407ace4b5b206dda6113d7d452f72059a084ea Mon Sep 17 00:00:00 2001 From: whitequark Date: Wed, 24 Jun 2015 11:46:15 +0300 Subject: [PATCH 051/369] Implement prelude. --- artiq/py2llvm/builtins.py | 17 +++++++++++------ artiq/py2llvm/prelude.py | 17 +++++++++++++++++ artiq/py2llvm/typing.py | 15 ++++++++------- lit-test/py2llvm/typing/prelude.py | 10 ++++++++++ 4 files changed, 46 insertions(+), 13 deletions(-) create mode 100644 artiq/py2llvm/prelude.py create mode 100644 lit-test/py2llvm/typing/prelude.py diff --git a/artiq/py2llvm/builtins.py b/artiq/py2llvm/builtins.py index daa94c70a..90e222374 100644 --- a/artiq/py2llvm/builtins.py +++ b/artiq/py2llvm/builtins.py @@ -1,6 +1,6 @@ """ -The :mod:`builtins` module contains the builtin Python and ARTIQ -types, such as int or float. +The :mod:`builtins` module contains the builtin Python +and ARTIQ types, such as int or float. """ from . import types @@ -32,16 +32,16 @@ class TList(types.TMono): super().__init__("list", {"elt": elt}) def fn_len(): - return types.TBuiltin("len") + return types.TBuiltin("function len") def fn_round(): - return types.TBuiltin("round") + return types.TBuiltin("function round") def fn_range(): - return types.TBuiltin("range") + return types.TBuiltin("function range") def fn_syscall(): - return types.TBuiltin("syscall") + return types.TBuiltin("function syscall") # Accessors @@ -79,3 +79,8 @@ def is_collection(typ): typ = typ.find() return isinstance(typ, types.TTuple) or \ types.is_mono(typ, "list") + +def is_function(typ, name): + typ = typ.find() + return isinstance(typ, types.TBuiltin) and \ + typ.name == "function " + name diff --git a/artiq/py2llvm/prelude.py b/artiq/py2llvm/prelude.py new file mode 100644 index 000000000..4e4d93fe7 --- /dev/null +++ b/artiq/py2llvm/prelude.py @@ -0,0 +1,17 @@ +""" +The :mod:`prelude` module contains the initial global environment +in which ARTIQ kernels are evaluated. +""" + +from . import builtins + +def globals(): + return { + "bool": builtins.TBool(), + "int": builtins.TInt(), + "float": builtins.TFloat(), + "round": builtins.fn_round(), + "len": builtins.fn_len(), + "range": builtins.fn_range(), + "syscall": builtins.fn_syscall(), + } diff --git a/artiq/py2llvm/typing.py b/artiq/py2llvm/typing.py index 3ceae8eb3..676dce879 100644 --- a/artiq/py2llvm/typing.py +++ b/artiq/py2llvm/typing.py @@ -24,7 +24,7 @@ class LocalExtractor(algorithm.Visitor): # parameters can't be declared as global or nonlocal self.params = set() - if len(self.env_stack) == 0: + if len(self.env_stack) == 1: self.env_stack.append(self.typing_env) def visit_in_assign(self, node): @@ -115,7 +115,7 @@ class LocalExtractor(algorithm.Visitor): self.global_.add(name) self._assignable(name) - self.env_stack[0][name] = self.typing_env[name] + self.env_stack[1][name] = self.typing_env[name] def visit_Nonlocal(self, node): for name, loc in zip(node.names, node.name_locs): @@ -123,9 +123,9 @@ class LocalExtractor(algorithm.Visitor): self._check_not_in(name, self.params, "a parameter", "nonlocal", loc): continue - # nonlocal does not search global scope + # nonlocal does not search prelude and global scopes found = False - for outer_env in reversed(self.env_stack[1:]): + for outer_env in reversed(self.env_stack[2:]): if name in outer_env: found = True break @@ -156,9 +156,9 @@ class ASTTypedRewriter(algorithm.Transformer): via :class:`LocalExtractor`. """ - def __init__(self, engine): + def __init__(self, engine, globals={}): self.engine = engine - self.env_stack = [] + self.env_stack = [globals] def _find_name(self, name, loc): for typing_env in reversed(self.env_stack): @@ -990,6 +990,7 @@ class Printer(algorithm.Visitor): def main(): import sys, fileinput, os + from . import prelude if len(sys.argv) > 1 and sys.argv[1] == '+diag': del sys.argv[1] @@ -1009,7 +1010,7 @@ def main(): buf = source.Buffer("".join(fileinput.input()).expandtabs(), os.path.basename(fileinput.filename())) parsed, comments = parse_buffer(buf, engine=engine) - typed = ASTTypedRewriter(engine=engine).visit(parsed) + typed = ASTTypedRewriter(globals=prelude.globals(), engine=engine).visit(parsed) Inferencer(engine=engine).visit(typed) printer = Printer(buf) diff --git a/lit-test/py2llvm/typing/prelude.py b/lit-test/py2llvm/typing/prelude.py new file mode 100644 index 000000000..d1229f66c --- /dev/null +++ b/lit-test/py2llvm/typing/prelude.py @@ -0,0 +1,10 @@ +# RUN: %python -m artiq.py2llvm.typing %s >%t +# RUN: OutputCheck %s --file-to-check=%t + +# CHECK-L: x: +x = len + +def f(): + global len + # CHECK-L: len:int(width='a) = + len = 1 From 752031147dba22b6ede2c762becfde2091c11d55 Mon Sep 17 00:00:00 2001 From: whitequark Date: Wed, 24 Jun 2015 12:16:17 +0300 Subject: [PATCH 052/369] Add valid forms for builtin calls. --- artiq/py2llvm/prelude.py | 2 +- artiq/py2llvm/types.py | 3 ++ artiq/py2llvm/typing.py | 62 ++++++++++++++++++++++++ lit-test/py2llvm/typing/builtin_calls.py | 2 + 4 files changed, 68 insertions(+), 1 deletion(-) create mode 100644 lit-test/py2llvm/typing/builtin_calls.py diff --git a/artiq/py2llvm/prelude.py b/artiq/py2llvm/prelude.py index 4e4d93fe7..4f603c05a 100644 --- a/artiq/py2llvm/prelude.py +++ b/artiq/py2llvm/prelude.py @@ -10,8 +10,8 @@ def globals(): "bool": builtins.TBool(), "int": builtins.TInt(), "float": builtins.TFloat(), - "round": builtins.fn_round(), "len": builtins.fn_len(), + "round": builtins.fn_round(), "range": builtins.fn_range(), "syscall": builtins.fn_syscall(), } diff --git a/artiq/py2llvm/types.py b/artiq/py2llvm/types.py index 029744ef8..60e09f2e3 100644 --- a/artiq/py2llvm/types.py +++ b/artiq/py2llvm/types.py @@ -262,6 +262,9 @@ def is_tuple(typ, elts=None): def is_function(typ): return isinstance(typ.find(), TFunction) +def is_builtin(typ): + return isinstance(typ.find(), TBuiltin) + def get_value(typ): typ = typ.find() if isinstance(typ, TVar): diff --git a/artiq/py2llvm/typing.py b/artiq/py2llvm/typing.py index 676dce879..bd7145e09 100644 --- a/artiq/py2llvm/typing.py +++ b/artiq/py2llvm/typing.py @@ -715,6 +715,66 @@ class Inferencer(algorithm.Visitor): self.generic_visit(node) self._unify_collection(element=node.target, collection=node.iter) + def visit_builtin_call(self, node): + if types.is_mono(node.func.type): + func_name = "function " + node.func.type.find().name + elif builtins.is_function(node.func.type): + func_name = node.func.type.find().name + + def valid_form(signature): + return diagnostic.Diagnostic("note", + "{func} can be invoked as: {signature}", + {"func": func_name}, + node.func.loc) + + def diagnose(valid_forms): + diag = diagnostic.Diagnostic("error", + "{func} cannot be invoked with these arguments", + {"func": func_name}, + node.func.loc, notes=valid_forms) + + if builtins.is_bool(node.type): + valid_forms = lambda: [ + valid_form("bool() -> bool"), + valid_form("bool(x:'a) -> bool where 'a is numeric") + ] + elif builtins.is_int(node.type): + valid_forms = lambda: [ + valid_form("int() -> int(width='a)"), + valid_form("int(x:'a) -> int(width='b) where 'a is numeric"), + valid_form("int(x:'a, width='b ) -> int(width='b) where 'a is numeric") + ] + elif builtins.is_float(node.type): + valid_forms = lambda: [ + valid_form("float() -> float"), + valid_form("float(x:'a) -> float where 'a is numeric") + ] + elif builtins.is_list(node.type): + valid_forms = lambda: [ + valid_form("list() -> list(elt='a)"), + # TODO: add this form when adding iterators + # valid_form("list(x) -> list(elt='a)") + ] + elif builtins.is_function(node.type, "len"): + valid_forms = lambda: [ + valid_form("len(x:list(elt='a)) -> int(width='b)"), + ] + elif builtins.is_function(node.type, "round"): + valid_forms = lambda: [ + valid_form("round(x:float) -> int(width='a)"), + ] + # TODO: add when there are range types + # elif builtins.is_function(node.type, "range"): + # valid_forms = lambda: [ + # valid_form("range(max:'a) -> range(elt='a)"), + # valid_form("range(min:'a, max:'a) -> range(elt='a)"), + # valid_form("range(min:'a, max:'a, step:'a) -> range(elt='a)"), + # ] + # TODO: add when it is clear what interface syscall() has + # elif builtins.is_function(node.type, "syscall"): + # valid_Forms = lambda: [ + # ] + def visit_CallT(self, node): self.generic_visit(node) @@ -729,6 +789,8 @@ class Inferencer(algorithm.Visitor): if types.is_var(node.func.type): return # not enough info yet + elif types.is_mono(node.func.type) or types.is_builtin(node.func.type): + return self.visit_builtin_call(self, node) elif not types.is_function(node.func.type): diag = diagnostic.Diagnostic("error", "cannot call this expression of type {type}", diff --git a/lit-test/py2llvm/typing/builtin_calls.py b/lit-test/py2llvm/typing/builtin_calls.py new file mode 100644 index 000000000..fb6ac38c0 --- /dev/null +++ b/lit-test/py2llvm/typing/builtin_calls.py @@ -0,0 +1,2 @@ +# RUN: %python -m artiq.py2llvm.typing %s >%t +# RUN: OutputCheck %s --file-to-check=%t From 7cd6011981240b01e9461ad99d51162199d09ff0 Mon Sep 17 00:00:00 2001 From: whitequark Date: Fri, 26 Jun 2015 11:16:08 +0300 Subject: [PATCH 053/369] Add typechecking for most builtin. --- artiq/py2llvm/builtins.py | 16 +++- artiq/py2llvm/prelude.py | 7 +- artiq/py2llvm/typing.py | 107 +++++++++++++++++++---- lit-test/py2llvm/typing/builtin_calls.py | 30 +++++++ 4 files changed, 138 insertions(+), 22 deletions(-) diff --git a/artiq/py2llvm/builtins.py b/artiq/py2llvm/builtins.py index 90e222374..13ffe745d 100644 --- a/artiq/py2llvm/builtins.py +++ b/artiq/py2llvm/builtins.py @@ -31,6 +31,18 @@ class TList(types.TMono): elt = types.TVar() super().__init__("list", {"elt": elt}) +def fn_bool(): + return types.TBuiltin("class bool") + +def fn_int(): + return types.TBuiltin("class int") + +def fn_float(): + return types.TBuiltin("class float") + +def fn_list(): + return types.TBuiltin("class list") + def fn_len(): return types.TBuiltin("function len") @@ -80,7 +92,7 @@ def is_collection(typ): return isinstance(typ, types.TTuple) or \ types.is_mono(typ, "list") -def is_function(typ, name): +def is_builtin(typ, name): typ = typ.find() return isinstance(typ, types.TBuiltin) and \ - typ.name == "function " + name + typ.name == name diff --git a/artiq/py2llvm/prelude.py b/artiq/py2llvm/prelude.py index 4f603c05a..3ecd63a69 100644 --- a/artiq/py2llvm/prelude.py +++ b/artiq/py2llvm/prelude.py @@ -7,9 +7,10 @@ from . import builtins def globals(): return { - "bool": builtins.TBool(), - "int": builtins.TInt(), - "float": builtins.TFloat(), + "bool": builtins.fn_bool(), + "int": builtins.fn_int(), + "float": builtins.fn_float(), + "list": builtins.fn_list(), "len": builtins.fn_len(), "round": builtins.fn_round(), "range": builtins.fn_range(), diff --git a/artiq/py2llvm/typing.py b/artiq/py2llvm/typing.py index bd7145e09..10c063d55 100644 --- a/artiq/py2llvm/typing.py +++ b/artiq/py2llvm/typing.py @@ -716,62 +716,135 @@ class Inferencer(algorithm.Visitor): self._unify_collection(element=node.target, collection=node.iter) def visit_builtin_call(self, node): - if types.is_mono(node.func.type): - func_name = "function " + node.func.type.find().name - elif builtins.is_function(node.func.type): - func_name = node.func.type.find().name + typ = node.func.type.find() def valid_form(signature): return diagnostic.Diagnostic("note", "{func} can be invoked as: {signature}", - {"func": func_name}, + {"func": typ.name, "signature": signature}, node.func.loc) def diagnose(valid_forms): diag = diagnostic.Diagnostic("error", "{func} cannot be invoked with these arguments", - {"func": func_name}, + {"func": typ.name}, node.func.loc, notes=valid_forms) + self.engine.process(diag) - if builtins.is_bool(node.type): + if builtins.is_builtin(typ, "class bool"): valid_forms = lambda: [ valid_form("bool() -> bool"), - valid_form("bool(x:'a) -> bool where 'a is numeric") + valid_form("bool(x:'a) -> bool") ] - elif builtins.is_int(node.type): + + 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 builtins.is_builtin(typ, "class int"): valid_forms = lambda: [ valid_form("int() -> int(width='a)"), valid_form("int(x:'a) -> int(width='b) where 'a is numeric"), - valid_form("int(x:'a, width='b ) -> int(width='b) where 'a is numeric") + valid_form("int(x:'a, width='b:) -> int(width='b) where 'a is numeric") ] - elif builtins.is_float(node.type): + + self._unify(node.type, builtins.TInt(), + 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 \ + builtins.is_numeric(node.args[0].type): + pass + elif len(node.args) == 1 and len(node.keywords) == 1 and \ + builtins.is_numeric(node.args[0].type) and \ + node.keywords[0].arg == 'width': + width = node.keywords[0].value + if not (isinstance(width, asttyped.NumT) and isinstance(width.n, int)): + diag = diagnostic.Diagnostic("error", + "the width argument of int() must be an integer literal", {}, + node.keywords[0].loc) + + self._unify(node.type, builtins.TInt(types.TValue(width.n)), + node.loc, None) + else: + diagnose(valid_forms()) + elif builtins.is_builtin(typ, "class float"): valid_forms = lambda: [ valid_form("float() -> float"), valid_form("float(x:'a) -> float where 'a is numeric") ] - elif builtins.is_list(node.type): + + 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 \ + builtins.is_numeric(node.args[0].type): + pass + else: + diagnose(valid_forms()) + elif builtins.is_builtin(typ, "class list"): valid_forms = lambda: [ valid_form("list() -> list(elt='a)"), # TODO: add this form when adding iterators # valid_form("list(x) -> list(elt='a)") ] - elif builtins.is_function(node.type, "len"): + + self._unify(node.type, builtins.TList(), + node.loc, None) + + if len(node.args) == 0 and len(node.keywords) == 0: + pass # [] + else: + diagnose(valid_forms()) + elif builtins.is_builtin(typ, "function len"): valid_forms = lambda: [ valid_form("len(x:list(elt='a)) -> int(width='b)"), ] - elif builtins.is_function(node.type, "round"): + + # TODO: should be ssize_t-sized + self._unify(node.type, builtins.TInt(types.TValue(32)), + node.loc, None) + + if len(node.args) == 1 and len(node.keywords) == 0: + arg, = node.args + + self._unify(arg.type, builtins.TList(), + arg.loc, None) + else: + diagnose(valid_forms()) + elif builtins.is_builtin(typ, "function round"): valid_forms = lambda: [ valid_form("round(x:float) -> int(width='a)"), ] + + 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()) # TODO: add when there are range types - # elif builtins.is_function(node.type, "range"): + # elif builtins.is_builtin(typ, "function range"): # valid_forms = lambda: [ # valid_form("range(max:'a) -> range(elt='a)"), # valid_form("range(min:'a, max:'a) -> range(elt='a)"), # valid_form("range(min:'a, max:'a, step:'a) -> range(elt='a)"), # ] # TODO: add when it is clear what interface syscall() has - # elif builtins.is_function(node.type, "syscall"): + # elif builtins.is_builtin(typ, "function syscall"): # valid_Forms = lambda: [ # ] @@ -790,7 +863,7 @@ class Inferencer(algorithm.Visitor): if types.is_var(node.func.type): return # not enough info yet elif types.is_mono(node.func.type) or types.is_builtin(node.func.type): - return self.visit_builtin_call(self, node) + return self.visit_builtin_call(node) elif not types.is_function(node.func.type): diag = diagnostic.Diagnostic("error", "cannot call this expression of type {type}", diff --git a/lit-test/py2llvm/typing/builtin_calls.py b/lit-test/py2llvm/typing/builtin_calls.py index fb6ac38c0..8405391f9 100644 --- a/lit-test/py2llvm/typing/builtin_calls.py +++ b/lit-test/py2llvm/typing/builtin_calls.py @@ -1,2 +1,32 @@ # RUN: %python -m artiq.py2llvm.typing %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:():int(width='b) +int() + +# CHECK-L: int:(1.0:float):int(width='c) +int(1.0) + +# CHECK-L: int:(1.0:float, width=64:int(width='d)):int(width=64) +int(1.0, width=64) + +# CHECK-L: float:():float +float() + +# CHECK-L: float:(1:int(width='e)):float +float(1) + +# CHECK-L: list:():list(elt='f) +list() + +# CHECK-L: len:([]:list(elt='g)):int(width=32) +len([]) + +# CHECK-L: round:(1.0:float):int(width='h) +round(1.0) From 71256a710997ed2bb675af4482389cc17a18a3f9 Mon Sep 17 00:00:00 2001 From: whitequark Date: Fri, 26 Jun 2015 18:35:34 +0300 Subject: [PATCH 054/369] Assignment rhs is typed before lhs. --- artiq/py2llvm/typing.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/artiq/py2llvm/typing.py b/artiq/py2llvm/typing.py index 10c063d55..926bd0377 100644 --- a/artiq/py2llvm/typing.py +++ b/artiq/py2llvm/typing.py @@ -35,13 +35,13 @@ class LocalExtractor(algorithm.Visitor): self.in_assign = False def visit_Assign(self, node): + self.visit(node.value) for target in node.targets: self.visit_in_assign(target) - self.visit(node.value) def visit_For(self, node): - self.visit_in_assign(node.target) self.visit(node.iter) + self.visit_in_assign(node.target) self.visit(node.body) self.visit(node.orelse) @@ -51,8 +51,8 @@ class LocalExtractor(algorithm.Visitor): self.visit_in_assign(node.optional_vars) def visit_comprehension(self, node): - self.visit_in_assign(node.target) self.visit(node.iter) + self.visit_in_assign(node.target) for if_ in node.ifs: self.visit(node.ifs) From e07057c224f929a3f02258427ec9ad8d38be0095 Mon Sep 17 00:00:00 2001 From: whitequark Date: Fri, 26 Jun 2015 18:53:20 +0300 Subject: [PATCH 055/369] Add range types. --- artiq/py2llvm/builtins.py | 27 ++++++- artiq/py2llvm/prelude.py | 2 +- artiq/py2llvm/typing.py | 79 ++++++++++++++----- .../py2llvm/typing/error_builtin_calls.py | 12 +++ lit-test/py2llvm/typing/error_iterable.py | 5 ++ 5 files changed, 100 insertions(+), 25 deletions(-) create mode 100644 lit-test/py2llvm/typing/error_builtin_calls.py create mode 100644 lit-test/py2llvm/typing/error_iterable.py diff --git a/artiq/py2llvm/builtins.py b/artiq/py2llvm/builtins.py index 13ffe745d..8875f64d7 100644 --- a/artiq/py2llvm/builtins.py +++ b/artiq/py2llvm/builtins.py @@ -31,6 +31,12 @@ class TList(types.TMono): elt = types.TVar() super().__init__("list", {"elt": elt}) +class TRange(types.TMono): + def __init__(self, elt=None): + if elt is None: + elt = types.TVar() + super().__init__("range", {"elt": elt}) + def fn_bool(): return types.TBuiltin("class bool") @@ -43,15 +49,15 @@ def fn_float(): def fn_list(): return types.TBuiltin("class list") +def fn_range(): + return types.TBuiltin("function range") + def fn_len(): return types.TBuiltin("function len") def fn_round(): return types.TBuiltin("function round") -def fn_range(): - return types.TBuiltin("function range") - def fn_syscall(): return types.TBuiltin("function syscall") @@ -87,6 +93,21 @@ def is_list(typ, elt=None): else: return types.is_mono(typ, "list") +def is_range(typ, elt=None): + if elt: + return types.is_mono(typ, "range", {"elt": elt}) + else: + return types.is_mono(typ, "range") + +def is_iterable(typ): + typ = typ.find() + return isinstance(typ, types.TMono) and \ + typ.name in ('list', 'range') + +def get_iterable_elt(typ): + if is_iterable(typ): + return typ.find()["elt"] + def is_collection(typ): typ = typ.find() return isinstance(typ, types.TTuple) or \ diff --git a/artiq/py2llvm/prelude.py b/artiq/py2llvm/prelude.py index 3ecd63a69..97a06a082 100644 --- a/artiq/py2llvm/prelude.py +++ b/artiq/py2llvm/prelude.py @@ -11,8 +11,8 @@ def globals(): "int": builtins.fn_int(), "float": builtins.fn_float(), "list": builtins.fn_list(), + "range": builtins.fn_range(), "len": builtins.fn_len(), "round": builtins.fn_round(), - "range": builtins.fn_range(), "syscall": builtins.fn_syscall(), } diff --git a/artiq/py2llvm/typing.py b/artiq/py2llvm/typing.py index 926bd0377..27ba7a1ac 100644 --- a/artiq/py2llvm/typing.py +++ b/artiq/py2llvm/typing.py @@ -454,14 +454,22 @@ class Inferencer(algorithm.Visitor): node.attr_loc, [node.value.loc]) self.engine.process(diag) - def _unify_collection(self, element, collection): - # TODO: support more than just lists - self._unify(builtins.TList(element.type), collection.type, - element.loc, collection.loc) + def _unify_iterable(self, element, collection): + if builtins.is_iterable(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_SubscriptT(self, node): self.generic_visit(node) - self._unify_collection(element=node, collection=node.value) + self._unify_iterable(element=node, collection=node.value) def visit_IfExpT(self, node): self.generic_visit(node) @@ -678,7 +686,7 @@ class Inferencer(algorithm.Visitor): left.loc, right.loc) elif all(map(lambda op: isinstance(op, (ast.In, ast.NotIn)), node.ops)): for left, right in pairs: - self._unify_collection(element=left, collection=right) + 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] @@ -713,7 +721,7 @@ class Inferencer(algorithm.Visitor): def visit_comprehension(self, node): self.generic_visit(node) - self._unify_collection(element=node.target, collection=node.iter) + self._unify_iterable(element=node.target, collection=node.iter) def visit_builtin_call(self, node): typ = node.func.type.find() @@ -770,6 +778,8 @@ class Inferencer(algorithm.Visitor): diag = diagnostic.Diagnostic("error", "the width argument of int() must be an integer literal", {}, node.keywords[0].loc) + self.engine.process(diag) + return self._unify(node.type, builtins.TInt(types.TValue(width.n)), node.loc, None) @@ -805,9 +815,36 @@ class Inferencer(algorithm.Visitor): pass # [] else: diagnose(valid_forms()) + elif builtins.is_builtin(typ, "function range"): + valid_forms = lambda: [ + valid_form("range(max:'a) -> range(elt='a)"), + valid_form("range(min:'a, max:'a) -> range(elt='a)"), + valid_form("range(min:'a, max:'a, step:'a) -> range(elt='a)"), + ] + + range_tvar = types.TVar() + self._unify(node.type, builtins.TRange(range_tvar), + 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_tvar, + arg.loc, None) + + if not builtins.is_numeric(arg.type): + note = diagnostic.Diagnostic("note", + "this expression has type {type}", + {"type": types.TypePrinter().name(arg.type)}, + arg.loc) + diag = diagnostic.Diagnostic("error", + "an argument of range() must be of a numeric type", {}, + node.func.loc, notes=[note]) + self.engine.process(diag) + else: + diagnose(valid_forms()) elif builtins.is_builtin(typ, "function len"): valid_forms = lambda: [ - valid_form("len(x:list(elt='a)) -> int(width='b)"), + valid_form("len(x:'a) -> int(width='b) where 'a is iterable"), ] # TODO: should be ssize_t-sized @@ -817,8 +854,17 @@ class Inferencer(algorithm.Visitor): if len(node.args) == 1 and len(node.keywords) == 0: arg, = node.args - self._unify(arg.type, builtins.TList(), - arg.loc, None) + if builtins.is_list(arg.type) or builtins.is_range(arg.type): + pass + 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 builtins.is_builtin(typ, "function round"): @@ -836,13 +882,6 @@ class Inferencer(algorithm.Visitor): arg.loc, None) else: diagnose(valid_forms()) - # TODO: add when there are range types - # elif builtins.is_builtin(typ, "function range"): - # valid_forms = lambda: [ - # valid_form("range(max:'a) -> range(elt='a)"), - # valid_form("range(min:'a, max:'a) -> range(elt='a)"), - # valid_form("range(min:'a, max:'a, step:'a) -> range(elt='a)"), - # ] # TODO: add when it is clear what interface syscall() has # elif builtins.is_builtin(typ, "function syscall"): # valid_Forms = lambda: [ @@ -862,7 +901,7 @@ class Inferencer(algorithm.Visitor): if types.is_var(node.func.type): return # not enough info yet - elif types.is_mono(node.func.type) or types.is_builtin(node.func.type): + elif types.is_builtin(node.func.type): return self.visit_builtin_call(node) elif not types.is_function(node.func.type): diag = diagnostic.Diagnostic("error", @@ -988,9 +1027,7 @@ class Inferencer(algorithm.Visitor): old_in_loop, self.in_loop = self.in_loop, True self.generic_visit(node) self.in_loop = old_in_loop - # TODO: support more than just lists - self._unify(builtins.TList(node.target.type), node.iter.type, - node.target.loc, node.iter.loc) + self._unify_iterable(node.target, node.iter) def visit_While(self, node): old_in_loop, self.in_loop = self.in_loop, True diff --git a/lit-test/py2llvm/typing/error_builtin_calls.py b/lit-test/py2llvm/typing/error_builtin_calls.py new file mode 100644 index 000000000..86cab873d --- /dev/null +++ b/lit-test/py2llvm/typing/error_builtin_calls.py @@ -0,0 +1,12 @@ +# RUN: %python -m artiq.py2llvm.typing +diag %s >%t +# RUN: OutputCheck %s --file-to-check=%t + +a = 1 +# CHECK-L: ${LINE:+1}: error: the width argument of int() must be an integer literal +int(1.0, width=a) + +# CHECK-L: ${LINE:+1}: error: the argument of len() must be of an iterable type +len(1) + +# CHECK-L: ${LINE:+1}: error: an argument of range() must be of a numeric type +range([]) diff --git a/lit-test/py2llvm/typing/error_iterable.py b/lit-test/py2llvm/typing/error_iterable.py new file mode 100644 index 000000000..63382614f --- /dev/null +++ b/lit-test/py2llvm/typing/error_iterable.py @@ -0,0 +1,5 @@ +# RUN: %python -m artiq.py2llvm.typing +diag %s >%t +# RUN: OutputCheck %s --file-to-check=%t + +# CHECK-L: ${LINE:+1}: error: type int(width='a) is not iterable +for x in 1: pass From ea0d11b8be9b31417429a6fbfe6d81674f2d2545 Mon Sep 17 00:00:00 2001 From: whitequark Date: Fri, 26 Jun 2015 19:14:24 +0300 Subject: [PATCH 056/369] Allow also passing iterables to lists. --- artiq/py2llvm/typing.py | 19 ++++++++++++++++--- .../py2llvm/typing/error_builtin_calls.py | 3 +++ 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/artiq/py2llvm/typing.py b/artiq/py2llvm/typing.py index 27ba7a1ac..775b0d1f2 100644 --- a/artiq/py2llvm/typing.py +++ b/artiq/py2llvm/typing.py @@ -804,8 +804,7 @@ class Inferencer(algorithm.Visitor): elif builtins.is_builtin(typ, "class list"): valid_forms = lambda: [ valid_form("list() -> list(elt='a)"), - # TODO: add this form when adding iterators - # valid_form("list(x) -> list(elt='a)") + valid_form("list(x:'a) -> list(elt='b) where 'a is iterable") ] self._unify(node.type, builtins.TList(), @@ -813,6 +812,20 @@ class Inferencer(algorithm.Visitor): 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): + pass + 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 list() must be of an iterable type", {}, + node.func.loc, notes=[note]) + self.engine.process(diag) else: diagnose(valid_forms()) elif builtins.is_builtin(typ, "function range"): @@ -854,7 +867,7 @@ class Inferencer(algorithm.Visitor): if len(node.args) == 1 and len(node.keywords) == 0: arg, = node.args - if builtins.is_list(arg.type) or builtins.is_range(arg.type): + if builtins.is_iterable(arg.type): pass else: note = diagnostic.Diagnostic("note", diff --git a/lit-test/py2llvm/typing/error_builtin_calls.py b/lit-test/py2llvm/typing/error_builtin_calls.py index 86cab873d..caf77b1a8 100644 --- a/lit-test/py2llvm/typing/error_builtin_calls.py +++ b/lit-test/py2llvm/typing/error_builtin_calls.py @@ -8,5 +8,8 @@ int(1.0, width=a) # 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: an argument of range() must be of a numeric type range([]) From 9044e889832b5292ce55ec470e5ee1956e17f0de Mon Sep 17 00:00:00 2001 From: whitequark Date: Sun, 28 Jun 2015 22:40:57 +0300 Subject: [PATCH 057/369] Elaborate hierarchy of builtins. --- artiq/py2llvm/builtins.py | 16 ++++++++-------- artiq/py2llvm/types.py | 24 ++++++++++++++++++++++-- artiq/py2llvm/typing.py | 16 ++++++++-------- lit-test/py2llvm/typing/builtin_calls.py | 20 ++++++++++---------- lit-test/py2llvm/typing/prelude.py | 2 +- 5 files changed, 49 insertions(+), 29 deletions(-) diff --git a/artiq/py2llvm/builtins.py b/artiq/py2llvm/builtins.py index 8875f64d7..7e4e71d76 100644 --- a/artiq/py2llvm/builtins.py +++ b/artiq/py2llvm/builtins.py @@ -38,28 +38,28 @@ class TRange(types.TMono): super().__init__("range", {"elt": elt}) def fn_bool(): - return types.TBuiltin("class bool") + return types.TConstructor("bool") def fn_int(): - return types.TBuiltin("class int") + return types.TConstructor("int") def fn_float(): - return types.TBuiltin("class float") + return types.TConstructor("float") def fn_list(): - return types.TBuiltin("class list") + return types.TConstructor("list") def fn_range(): - return types.TBuiltin("function range") + return types.TBuiltinFunction("range") def fn_len(): - return types.TBuiltin("function len") + return types.TBuiltinFunction("len") def fn_round(): - return types.TBuiltin("function round") + return types.TBuiltinFunction("round") def fn_syscall(): - return types.TBuiltin("function syscall") + return types.TBuiltinFunction("syscall") # Accessors diff --git a/artiq/py2llvm/types.py b/artiq/py2llvm/types.py index 60e09f2e3..4f1bc4dfc 100644 --- a/artiq/py2llvm/types.py +++ b/artiq/py2llvm/types.py @@ -209,6 +209,24 @@ class TBuiltin(Type): def __ne__(self, other): return not (self == other) +class TBuiltinFunction(TBuiltin): + """ + A type of a builtin function. + """ + +class TConstructor(TBuiltin): + """ + A type of a constructor of a builtin class, e.g. ``list``. + Note that this is not the same as the type of an instance of + the class, which is ``TMono("list", ...)``. + """ + +class TExceptionConstructor(TBuiltin): + """ + A type of a constructor of a builtin 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 TValue(Type): """ @@ -306,8 +324,10 @@ class TypePrinter(object): args += [ "%s:%s" % (arg, self.name(typ.args[arg])) for arg in typ.args] args += ["?%s:%s" % (arg, self.name(typ.optargs[arg])) for arg in typ.optargs] return "(%s)->%s" % (", ".join(args), self.name(typ.ret)) - elif isinstance(typ, TBuiltin): - return "" % typ.name + elif isinstance(typ, TBuiltinFunction): + return "" % typ.name + elif isinstance(typ, (TConstructor, TExceptionConstructor)): + return "" % typ.name elif isinstance(typ, TValue): return repr(typ.value) else: diff --git a/artiq/py2llvm/typing.py b/artiq/py2llvm/typing.py index 775b0d1f2..f16191fd8 100644 --- a/artiq/py2llvm/typing.py +++ b/artiq/py2llvm/typing.py @@ -739,7 +739,7 @@ class Inferencer(algorithm.Visitor): node.func.loc, notes=valid_forms) self.engine.process(diag) - if builtins.is_builtin(typ, "class bool"): + if builtins.is_builtin(typ, "bool"): valid_forms = lambda: [ valid_form("bool() -> bool"), valid_form("bool(x:'a) -> bool") @@ -755,7 +755,7 @@ class Inferencer(algorithm.Visitor): self._unify(node.type, builtins.TBool(), node.loc, None) - elif builtins.is_builtin(typ, "class int"): + elif builtins.is_builtin(typ, "int"): valid_forms = lambda: [ valid_form("int() -> int(width='a)"), valid_form("int(x:'a) -> int(width='b) where 'a is numeric"), @@ -785,7 +785,7 @@ class Inferencer(algorithm.Visitor): node.loc, None) else: diagnose(valid_forms()) - elif builtins.is_builtin(typ, "class float"): + elif builtins.is_builtin(typ, "float"): valid_forms = lambda: [ valid_form("float() -> float"), valid_form("float(x:'a) -> float where 'a is numeric") @@ -801,7 +801,7 @@ class Inferencer(algorithm.Visitor): pass else: diagnose(valid_forms()) - elif builtins.is_builtin(typ, "class list"): + elif builtins.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") @@ -828,7 +828,7 @@ class Inferencer(algorithm.Visitor): self.engine.process(diag) else: diagnose(valid_forms()) - elif builtins.is_builtin(typ, "function range"): + elif builtins.is_builtin(typ, "range"): valid_forms = lambda: [ valid_form("range(max:'a) -> range(elt='a)"), valid_form("range(min:'a, max:'a) -> range(elt='a)"), @@ -855,7 +855,7 @@ class Inferencer(algorithm.Visitor): self.engine.process(diag) else: diagnose(valid_forms()) - elif builtins.is_builtin(typ, "function len"): + elif builtins.is_builtin(typ, "len"): valid_forms = lambda: [ valid_form("len(x:'a) -> int(width='b) where 'a is iterable"), ] @@ -880,7 +880,7 @@ class Inferencer(algorithm.Visitor): self.engine.process(diag) else: diagnose(valid_forms()) - elif builtins.is_builtin(typ, "function round"): + elif builtins.is_builtin(typ, "round"): valid_forms = lambda: [ valid_form("round(x:float) -> int(width='a)"), ] @@ -896,7 +896,7 @@ class Inferencer(algorithm.Visitor): else: diagnose(valid_forms()) # TODO: add when it is clear what interface syscall() has - # elif builtins.is_builtin(typ, "function syscall"): + # elif builtins.is_builtin(typ, "syscall"): # valid_Forms = lambda: [ # ] diff --git a/lit-test/py2llvm/typing/builtin_calls.py b/lit-test/py2llvm/typing/builtin_calls.py index 8405391f9..7d72895e6 100644 --- a/lit-test/py2llvm/typing/builtin_calls.py +++ b/lit-test/py2llvm/typing/builtin_calls.py @@ -1,32 +1,32 @@ # RUN: %python -m artiq.py2llvm.typing %s >%t # RUN: OutputCheck %s --file-to-check=%t -# CHECK-L: bool:():bool +# CHECK-L: bool:():bool bool() -# CHECK-L: bool:([]:list(elt='a)):bool +# CHECK-L: bool:([]:list(elt='a)):bool bool([]) -# CHECK-L: int:():int(width='b) +# CHECK-L: int:():int(width='b) int() -# CHECK-L: int:(1.0:float):int(width='c) +# CHECK-L: int:(1.0:float):int(width='c) int(1.0) -# CHECK-L: int:(1.0:float, width=64:int(width='d)):int(width=64) +# CHECK-L: int:(1.0:float, width=64:int(width='d)):int(width=64) int(1.0, width=64) -# CHECK-L: float:():float +# CHECK-L: float:():float float() -# CHECK-L: float:(1:int(width='e)):float +# CHECK-L: float:(1:int(width='e)):float float(1) -# CHECK-L: list:():list(elt='f) +# CHECK-L: list:():list(elt='f) list() -# CHECK-L: len:([]:list(elt='g)):int(width=32) +# CHECK-L: len:([]:list(elt='g)):int(width=32) len([]) -# CHECK-L: round:(1.0:float):int(width='h) +# CHECK-L: round:(1.0:float):int(width='h) round(1.0) diff --git a/lit-test/py2llvm/typing/prelude.py b/lit-test/py2llvm/typing/prelude.py index d1229f66c..007c8d3a4 100644 --- a/lit-test/py2llvm/typing/prelude.py +++ b/lit-test/py2llvm/typing/prelude.py @@ -1,7 +1,7 @@ # RUN: %python -m artiq.py2llvm.typing %s >%t # RUN: OutputCheck %s --file-to-check=%t -# CHECK-L: x: +# CHECK-L: x: x = len def f(): From a4a9cd884e7ca0227434cf2530311e2089c271d0 Mon Sep 17 00:00:00 2001 From: whitequark Date: Sun, 28 Jun 2015 22:48:15 +0300 Subject: [PATCH 058/369] Add exception constructor types. --- artiq/py2llvm/builtins.py | 21 ++++++++++++++++++--- artiq/py2llvm/prelude.py | 17 +++++++++-------- lit-test/py2llvm/typing/exception.py | 5 +++++ 3 files changed, 32 insertions(+), 11 deletions(-) create mode 100644 lit-test/py2llvm/typing/exception.py diff --git a/artiq/py2llvm/builtins.py b/artiq/py2llvm/builtins.py index 7e4e71d76..c1dcfb7ba 100644 --- a/artiq/py2llvm/builtins.py +++ b/artiq/py2llvm/builtins.py @@ -37,6 +37,10 @@ class TRange(types.TMono): elt = types.TVar() super().__init__("range", {"elt": elt}) +class TException(types.TMono): + def __init__(self): + super().__init__("Exception") + def fn_bool(): return types.TConstructor("bool") @@ -49,6 +53,9 @@ def fn_float(): def fn_list(): return types.TConstructor("list") +def fn_Exception(): + return types.TExceptionConstructor("Exception") + def fn_range(): return types.TBuiltinFunction("range") @@ -70,7 +77,7 @@ def is_bool(typ): return types.is_mono(typ, "bool") def is_int(typ, width=None): - if width: + if width is not None: return types.is_mono(typ, "int", {"width": width}) else: return types.is_mono(typ, "int") @@ -88,13 +95,13 @@ def is_numeric(typ): typ.name in ('int', 'float') def is_list(typ, elt=None): - if elt: + if elt is not None: return types.is_mono(typ, "list", {"elt": elt}) else: return types.is_mono(typ, "list") def is_range(typ, elt=None): - if elt: + if elt is not None: return types.is_mono(typ, "range", {"elt": elt}) else: return types.is_mono(typ, "range") @@ -117,3 +124,11 @@ def is_builtin(typ, name): typ = typ.find() return isinstance(typ, types.TBuiltin) and \ typ.name == name + +def is_exception(typ, name=None): + typ = typ.find() + if name is not None: + return isinstance(typ, types.TExceptionConstructor) and \ + typ.name == name + else: + return isinstance(typ, types.TExceptionConstructor) diff --git a/artiq/py2llvm/prelude.py b/artiq/py2llvm/prelude.py index 97a06a082..d024c2423 100644 --- a/artiq/py2llvm/prelude.py +++ b/artiq/py2llvm/prelude.py @@ -7,12 +7,13 @@ from . import builtins def globals(): return { - "bool": builtins.fn_bool(), - "int": builtins.fn_int(), - "float": builtins.fn_float(), - "list": builtins.fn_list(), - "range": builtins.fn_range(), - "len": builtins.fn_len(), - "round": builtins.fn_round(), - "syscall": builtins.fn_syscall(), + "bool": builtins.fn_bool(), + "int": builtins.fn_int(), + "float": builtins.fn_float(), + "list": builtins.fn_list(), + "range": builtins.fn_range(), + "Exception": builtins.fn_Exception(), + "len": builtins.fn_len(), + "round": builtins.fn_round(), + "syscall": builtins.fn_syscall(), } diff --git a/lit-test/py2llvm/typing/exception.py b/lit-test/py2llvm/typing/exception.py new file mode 100644 index 000000000..5500d96a0 --- /dev/null +++ b/lit-test/py2llvm/typing/exception.py @@ -0,0 +1,5 @@ +# RUN: %python -m artiq.py2llvm.typing %s >%t +# RUN: OutputCheck %s --file-to-check=%t + +# CHECK-L: Exception: +Exception From f430bf3f638b793d7365a8fcd5abc4cd060eec59 Mon Sep 17 00:00:00 2001 From: whitequark Date: Mon, 29 Jun 2015 00:31:06 +0300 Subject: [PATCH 059/369] Add support for exceptions. --- artiq/py2llvm/asttyped.py | 4 +++ artiq/py2llvm/builtins.py | 2 +- artiq/py2llvm/types.py | 3 ++ artiq/py2llvm/typing.py | 41 +++++++++++++++++++++- lit-test/py2llvm/typing/error_exception.py | 14 ++++++++ lit-test/py2llvm/typing/exception.py | 8 +++++ 6 files changed, 70 insertions(+), 2 deletions(-) create mode 100644 lit-test/py2llvm/typing/error_exception.py diff --git a/artiq/py2llvm/asttyped.py b/artiq/py2llvm/asttyped.py index a9762ac4e..c592161e6 100644 --- a/artiq/py2llvm/asttyped.py +++ b/artiq/py2llvm/asttyped.py @@ -33,6 +33,10 @@ class FunctionDefT(ast.FunctionDef, scoped): class ModuleT(ast.Module, scoped): pass +class ExceptHandlerT(ast.ExceptHandler, commontyped): + _fields = ("filter", "name", "body") # rename ast.ExceptHandler.type + _types = ("name_type",) + class AttributeT(ast.Attribute, commontyped): pass class BinOpT(ast.BinOp, commontyped): diff --git a/artiq/py2llvm/builtins.py b/artiq/py2llvm/builtins.py index c1dcfb7ba..9e44e0da7 100644 --- a/artiq/py2llvm/builtins.py +++ b/artiq/py2llvm/builtins.py @@ -125,7 +125,7 @@ def is_builtin(typ, name): return isinstance(typ, types.TBuiltin) and \ typ.name == name -def is_exception(typ, name=None): +def is_exn_constructor(typ, name=None): typ = typ.find() if name is not None: return isinstance(typ, types.TExceptionConstructor) and \ diff --git a/artiq/py2llvm/types.py b/artiq/py2llvm/types.py index 4f1bc4dfc..388b1d6ff 100644 --- a/artiq/py2llvm/types.py +++ b/artiq/py2llvm/types.py @@ -227,6 +227,9 @@ class TExceptionConstructor(TBuiltin): Note that this is not the same as the type of an instance of the class, which is ``TMono("Exception", ...)``. """ + def to_exception_type(self): + # Exceptions currently can't have type parameters + return TMono(self.name, {}) class TValue(Type): """ diff --git a/artiq/py2llvm/typing.py b/artiq/py2llvm/typing.py index f16191fd8..dc4ffdb33 100644 --- a/artiq/py2llvm/typing.py +++ b/artiq/py2llvm/typing.py @@ -328,6 +328,15 @@ class ASTTypedRewriter(algorithm.Transformer): finally: self.env_stack.pop() + def visit_ExceptHandler(self, node): + node = self.generic_visit(node) + node = asttyped.ExceptHandlerT( + name_type=self._find_name(node.name, node.name_loc), + 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: @@ -363,7 +372,6 @@ class ASTTypedRewriter(algorithm.Transformer): visit_Delete = visit_unsupported visit_Import = visit_unsupported visit_ImportFrom = visit_unsupported - visit_Try = visit_unsupported class Inferencer(algorithm.Visitor): @@ -1070,6 +1078,30 @@ class Inferencer(algorithm.Visitor): node.context_expr.loc) self.engine.process(diag) + def visit_ExceptHandlerT(self, node): + self.generic_visit(node) + + if not builtins.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.to_exception_type(), + node.name_loc, node.filter.loc, makenotes) + def _type_from_arguments(self, node, ret): self.generic_visit(node) @@ -1166,6 +1198,13 @@ class Printer(algorithm.Visitor): 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 generic_visit(self, node): super().generic_visit(node) diff --git a/lit-test/py2llvm/typing/error_exception.py b/lit-test/py2llvm/typing/error_exception.py new file mode 100644 index 000000000..e418b9b8a --- /dev/null +++ b/lit-test/py2llvm/typing/error_exception.py @@ -0,0 +1,14 @@ +# RUN: %python -m artiq.py2llvm.typing +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 int(width='a) with Exception +except Exception as e: + e = 1 diff --git a/lit-test/py2llvm/typing/exception.py b/lit-test/py2llvm/typing/exception.py index 5500d96a0..e7ce38b8d 100644 --- a/lit-test/py2llvm/typing/exception.py +++ b/lit-test/py2llvm/typing/exception.py @@ -3,3 +3,11 @@ # CHECK-L: Exception: Exception + +try: + pass +except Exception: + pass +except Exception as e: + # CHECK-L: e:Exception + e From 6bf95397d798fd47f4278962d4cd9faea2b34fa3 Mon Sep 17 00:00:00 2001 From: whitequark Date: Mon, 29 Jun 2015 20:12:09 +0300 Subject: [PATCH 060/369] Rename package py2llvm to compiler. Since the package implements a typechecker along with a code generator, and the typechecker will be run before or together with transformations, this name is more descriptive. --- artiq/{py2llvm => compiler}/__init__.py | 0 artiq/{py2llvm => compiler}/asttyped.py | 0 artiq/{py2llvm => compiler}/builtins.py | 0 artiq/{py2llvm => compiler}/prelude.py | 0 artiq/{py2llvm => compiler}/types.py | 0 artiq/{py2llvm => compiler}/typing.py | 2 +- lit-test/{py2llvm => compiler}/typing/builtin_calls.py | 2 +- lit-test/{py2llvm => compiler}/typing/coerce.py | 2 +- lit-test/{py2llvm => compiler}/typing/error_builtin_calls.py | 2 +- lit-test/{py2llvm => compiler}/typing/error_call.py | 2 +- lit-test/{py2llvm => compiler}/typing/error_coerce.py | 4 ++-- lit-test/{py2llvm => compiler}/typing/error_control_flow.py | 2 +- lit-test/{py2llvm => compiler}/typing/error_exception.py | 2 +- lit-test/{py2llvm => compiler}/typing/error_iterable.py | 2 +- lit-test/{py2llvm => compiler}/typing/error_local_unbound.py | 2 +- lit-test/{py2llvm => compiler}/typing/error_locals.py | 2 +- lit-test/{py2llvm => compiler}/typing/error_return.py | 2 +- lit-test/{py2llvm => compiler}/typing/error_unify.py | 2 +- lit-test/{py2llvm => compiler}/typing/exception.py | 2 +- lit-test/{py2llvm => compiler}/typing/gcd.py | 2 +- lit-test/{py2llvm => compiler}/typing/prelude.py | 2 +- lit-test/{py2llvm => compiler}/typing/scoping.py | 2 +- lit-test/{py2llvm => compiler}/typing/unify.py | 2 +- 23 files changed, 19 insertions(+), 19 deletions(-) rename artiq/{py2llvm => compiler}/__init__.py (100%) rename artiq/{py2llvm => compiler}/asttyped.py (100%) rename artiq/{py2llvm => compiler}/builtins.py (100%) rename artiq/{py2llvm => compiler}/prelude.py (100%) rename artiq/{py2llvm => compiler}/types.py (100%) rename artiq/{py2llvm => compiler}/typing.py (99%) rename lit-test/{py2llvm => compiler}/typing/builtin_calls.py (93%) rename lit-test/{py2llvm => compiler}/typing/coerce.py (96%) rename lit-test/{py2llvm => compiler}/typing/error_builtin_calls.py (88%) rename lit-test/{py2llvm => compiler}/typing/error_call.py (89%) rename lit-test/{py2llvm => compiler}/typing/error_coerce.py (92%) rename lit-test/{py2llvm => compiler}/typing/error_control_flow.py (89%) rename lit-test/{py2llvm => compiler}/typing/error_exception.py (84%) rename lit-test/{py2llvm => compiler}/typing/error_iterable.py (70%) rename lit-test/{py2llvm => compiler}/typing/error_local_unbound.py (67%) rename lit-test/{py2llvm => compiler}/typing/error_locals.py (94%) rename lit-test/{py2llvm => compiler}/typing/error_return.py (90%) rename lit-test/{py2llvm => compiler}/typing/error_unify.py (93%) rename lit-test/{py2llvm => compiler}/typing/exception.py (80%) rename lit-test/{py2llvm => compiler}/typing/gcd.py (84%) rename lit-test/{py2llvm => compiler}/typing/prelude.py (76%) rename lit-test/{py2llvm => compiler}/typing/scoping.py (69%) rename lit-test/{py2llvm => compiler}/typing/unify.py (95%) diff --git a/artiq/py2llvm/__init__.py b/artiq/compiler/__init__.py similarity index 100% rename from artiq/py2llvm/__init__.py rename to artiq/compiler/__init__.py diff --git a/artiq/py2llvm/asttyped.py b/artiq/compiler/asttyped.py similarity index 100% rename from artiq/py2llvm/asttyped.py rename to artiq/compiler/asttyped.py diff --git a/artiq/py2llvm/builtins.py b/artiq/compiler/builtins.py similarity index 100% rename from artiq/py2llvm/builtins.py rename to artiq/compiler/builtins.py diff --git a/artiq/py2llvm/prelude.py b/artiq/compiler/prelude.py similarity index 100% rename from artiq/py2llvm/prelude.py rename to artiq/compiler/prelude.py diff --git a/artiq/py2llvm/types.py b/artiq/compiler/types.py similarity index 100% rename from artiq/py2llvm/types.py rename to artiq/compiler/types.py diff --git a/artiq/py2llvm/typing.py b/artiq/compiler/typing.py similarity index 99% rename from artiq/py2llvm/typing.py rename to artiq/compiler/typing.py index dc4ffdb33..3c8c62f76 100644 --- a/artiq/py2llvm/typing.py +++ b/artiq/compiler/typing.py @@ -639,7 +639,7 @@ class Inferencer(algorithm.Visitor): 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", - "py2llvm does not support passing tuples to '*'", {}, + "passing tuples to '*' is not supported", {}, op.loc, [tuple_.loc]) self.engine.process(diag) return diff --git a/lit-test/py2llvm/typing/builtin_calls.py b/lit-test/compiler/typing/builtin_calls.py similarity index 93% rename from lit-test/py2llvm/typing/builtin_calls.py rename to lit-test/compiler/typing/builtin_calls.py index 7d72895e6..82c750bf1 100644 --- a/lit-test/py2llvm/typing/builtin_calls.py +++ b/lit-test/compiler/typing/builtin_calls.py @@ -1,4 +1,4 @@ -# RUN: %python -m artiq.py2llvm.typing %s >%t +# RUN: %python -m artiq.compiler.typing %s >%t # RUN: OutputCheck %s --file-to-check=%t # CHECK-L: bool:():bool diff --git a/lit-test/py2llvm/typing/coerce.py b/lit-test/compiler/typing/coerce.py similarity index 96% rename from lit-test/py2llvm/typing/coerce.py rename to lit-test/compiler/typing/coerce.py index 34df20e7e..5b161ab30 100644 --- a/lit-test/py2llvm/typing/coerce.py +++ b/lit-test/compiler/typing/coerce.py @@ -1,4 +1,4 @@ -# RUN: %python -m artiq.py2llvm.typing %s >%t +# RUN: %python -m artiq.compiler.typing %s >%t # RUN: OutputCheck %s --file-to-check=%t 1 | 2 diff --git a/lit-test/py2llvm/typing/error_builtin_calls.py b/lit-test/compiler/typing/error_builtin_calls.py similarity index 88% rename from lit-test/py2llvm/typing/error_builtin_calls.py rename to lit-test/compiler/typing/error_builtin_calls.py index caf77b1a8..858df6d4a 100644 --- a/lit-test/py2llvm/typing/error_builtin_calls.py +++ b/lit-test/compiler/typing/error_builtin_calls.py @@ -1,4 +1,4 @@ -# RUN: %python -m artiq.py2llvm.typing +diag %s >%t +# RUN: %python -m artiq.compiler.typing +diag %s >%t # RUN: OutputCheck %s --file-to-check=%t a = 1 diff --git a/lit-test/py2llvm/typing/error_call.py b/lit-test/compiler/typing/error_call.py similarity index 89% rename from lit-test/py2llvm/typing/error_call.py rename to lit-test/compiler/typing/error_call.py index 2d95078ac..2ff169faf 100644 --- a/lit-test/py2llvm/typing/error_call.py +++ b/lit-test/compiler/typing/error_call.py @@ -1,4 +1,4 @@ -# RUN: %python -m artiq.py2llvm.typing +diag %s >%t +# RUN: %python -m artiq.compiler.typing +diag %s >%t # RUN: OutputCheck %s --file-to-check=%t # CHECK-L: ${LINE:+1}: error: cannot call this expression of type int diff --git a/lit-test/py2llvm/typing/error_coerce.py b/lit-test/compiler/typing/error_coerce.py similarity index 92% rename from lit-test/py2llvm/typing/error_coerce.py rename to lit-test/compiler/typing/error_coerce.py index 0ac65724f..feb7447b8 100644 --- a/lit-test/py2llvm/typing/error_coerce.py +++ b/lit-test/compiler/typing/error_coerce.py @@ -1,4 +1,4 @@ -# RUN: %python -m artiq.py2llvm.typing +diag %s >%t +# RUN: %python -m artiq.compiler.typing +diag %s >%t # RUN: OutputCheck %s --file-to-check=%t # CHECK-L: ${LINE:+1}: error: expected '<<' operand to be of integer type, not float @@ -17,7 +17,7 @@ # CHECK-L: ${LINE:+1}: note: int(width='b), which cannot be added to a tuple (1,) + 2 -# CHECK-L: ${LINE:+1}: error: py2llvm does not support passing tuples to '*' +# 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 diff --git a/lit-test/py2llvm/typing/error_control_flow.py b/lit-test/compiler/typing/error_control_flow.py similarity index 89% rename from lit-test/py2llvm/typing/error_control_flow.py rename to lit-test/compiler/typing/error_control_flow.py index ea72347dd..c5a913bdd 100644 --- a/lit-test/py2llvm/typing/error_control_flow.py +++ b/lit-test/compiler/typing/error_control_flow.py @@ -1,4 +1,4 @@ -# RUN: %python -m artiq.py2llvm.typing +diag %s >%t +# RUN: %python -m artiq.compiler.typing +diag %s >%t # RUN: OutputCheck %s --file-to-check=%t # CHECK-L: ${LINE:+1}: error: return statement outside of a function diff --git a/lit-test/py2llvm/typing/error_exception.py b/lit-test/compiler/typing/error_exception.py similarity index 84% rename from lit-test/py2llvm/typing/error_exception.py rename to lit-test/compiler/typing/error_exception.py index e418b9b8a..4172f7bde 100644 --- a/lit-test/py2llvm/typing/error_exception.py +++ b/lit-test/compiler/typing/error_exception.py @@ -1,4 +1,4 @@ -# RUN: %python -m artiq.py2llvm.typing +diag %s >%t +# RUN: %python -m artiq.compiler.typing +diag %s >%t # RUN: OutputCheck %s --file-to-check=%t try: diff --git a/lit-test/py2llvm/typing/error_iterable.py b/lit-test/compiler/typing/error_iterable.py similarity index 70% rename from lit-test/py2llvm/typing/error_iterable.py rename to lit-test/compiler/typing/error_iterable.py index 63382614f..d3334a616 100644 --- a/lit-test/py2llvm/typing/error_iterable.py +++ b/lit-test/compiler/typing/error_iterable.py @@ -1,4 +1,4 @@ -# RUN: %python -m artiq.py2llvm.typing +diag %s >%t +# RUN: %python -m artiq.compiler.typing +diag %s >%t # RUN: OutputCheck %s --file-to-check=%t # CHECK-L: ${LINE:+1}: error: type int(width='a) is not iterable diff --git a/lit-test/py2llvm/typing/error_local_unbound.py b/lit-test/compiler/typing/error_local_unbound.py similarity index 67% rename from lit-test/py2llvm/typing/error_local_unbound.py rename to lit-test/compiler/typing/error_local_unbound.py index ceb61da83..fdf1b3047 100644 --- a/lit-test/py2llvm/typing/error_local_unbound.py +++ b/lit-test/compiler/typing/error_local_unbound.py @@ -1,4 +1,4 @@ -# RUN: %python -m artiq.py2llvm.typing +diag %s >%t +# RUN: %python -m artiq.compiler.typing +diag %s >%t # RUN: OutputCheck %s --file-to-check=%t # CHECK-L: ${LINE:+1}: fatal: name 'x' is not bound to anything diff --git a/lit-test/py2llvm/typing/error_locals.py b/lit-test/compiler/typing/error_locals.py similarity index 94% rename from lit-test/py2llvm/typing/error_locals.py rename to lit-test/compiler/typing/error_locals.py index 836029b55..048295d2c 100644 --- a/lit-test/py2llvm/typing/error_locals.py +++ b/lit-test/compiler/typing/error_locals.py @@ -1,4 +1,4 @@ -# RUN: %python -m artiq.py2llvm.typing +diag %s >%t +# RUN: %python -m artiq.compiler.typing +diag %s >%t # RUN: OutputCheck %s --file-to-check=%t x = 1 diff --git a/lit-test/py2llvm/typing/error_return.py b/lit-test/compiler/typing/error_return.py similarity index 90% rename from lit-test/py2llvm/typing/error_return.py rename to lit-test/compiler/typing/error_return.py index a84fe8d22..fff45ed0b 100644 --- a/lit-test/py2llvm/typing/error_return.py +++ b/lit-test/compiler/typing/error_return.py @@ -1,4 +1,4 @@ -# RUN: %python -m artiq.py2llvm.typing +diag %s >%t +# RUN: %python -m artiq.compiler.typing +diag %s >%t # RUN: OutputCheck %s --file-to-check=%t # CHECK-L: ${LINE:+2}: error: cannot unify int(width='a) with NoneType diff --git a/lit-test/py2llvm/typing/error_unify.py b/lit-test/compiler/typing/error_unify.py similarity index 93% rename from lit-test/py2llvm/typing/error_unify.py rename to lit-test/compiler/typing/error_unify.py index 3abef5b13..076be96f4 100644 --- a/lit-test/py2llvm/typing/error_unify.py +++ b/lit-test/compiler/typing/error_unify.py @@ -1,4 +1,4 @@ -# RUN: %python -m artiq.py2llvm.typing +diag %s >%t +# RUN: %python -m artiq.compiler.typing +diag %s >%t # RUN: OutputCheck %s --file-to-check=%t a = 1 diff --git a/lit-test/py2llvm/typing/exception.py b/lit-test/compiler/typing/exception.py similarity index 80% rename from lit-test/py2llvm/typing/exception.py rename to lit-test/compiler/typing/exception.py index e7ce38b8d..32046157f 100644 --- a/lit-test/py2llvm/typing/exception.py +++ b/lit-test/compiler/typing/exception.py @@ -1,4 +1,4 @@ -# RUN: %python -m artiq.py2llvm.typing %s >%t +# RUN: %python -m artiq.compiler.typing %s >%t # RUN: OutputCheck %s --file-to-check=%t # CHECK-L: Exception: diff --git a/lit-test/py2llvm/typing/gcd.py b/lit-test/compiler/typing/gcd.py similarity index 84% rename from lit-test/py2llvm/typing/gcd.py rename to lit-test/compiler/typing/gcd.py index 08c589535..c79052d89 100644 --- a/lit-test/py2llvm/typing/gcd.py +++ b/lit-test/compiler/typing/gcd.py @@ -1,4 +1,4 @@ -# RUN: %python -m artiq.py2llvm.typing %s >%t +# RUN: %python -m artiq.compiler.typing %s >%t def _gcd(a, b): if a < 0: diff --git a/lit-test/py2llvm/typing/prelude.py b/lit-test/compiler/typing/prelude.py similarity index 76% rename from lit-test/py2llvm/typing/prelude.py rename to lit-test/compiler/typing/prelude.py index 007c8d3a4..1b70be299 100644 --- a/lit-test/py2llvm/typing/prelude.py +++ b/lit-test/compiler/typing/prelude.py @@ -1,4 +1,4 @@ -# RUN: %python -m artiq.py2llvm.typing %s >%t +# RUN: %python -m artiq.compiler.typing %s >%t # RUN: OutputCheck %s --file-to-check=%t # CHECK-L: x: diff --git a/lit-test/py2llvm/typing/scoping.py b/lit-test/compiler/typing/scoping.py similarity index 69% rename from lit-test/py2llvm/typing/scoping.py rename to lit-test/compiler/typing/scoping.py index 96eefeb14..1d1da612c 100644 --- a/lit-test/py2llvm/typing/scoping.py +++ b/lit-test/compiler/typing/scoping.py @@ -1,4 +1,4 @@ -# RUN: %python -m artiq.py2llvm.typing %s >%t +# RUN: %python -m artiq.compiler.typing %s >%t # RUN: OutputCheck %s --file-to-check=%t def f(): diff --git a/lit-test/py2llvm/typing/unify.py b/lit-test/compiler/typing/unify.py similarity index 95% rename from lit-test/py2llvm/typing/unify.py rename to lit-test/compiler/typing/unify.py index f41fb10d5..9621c5b59 100644 --- a/lit-test/py2llvm/typing/unify.py +++ b/lit-test/compiler/typing/unify.py @@ -1,4 +1,4 @@ -# RUN: %python -m artiq.py2llvm.typing %s >%t +# RUN: %python -m artiq.compiler.typing %s >%t # RUN: OutputCheck %s --file-to-check=%t a = 1 From 86cdc84f7e54c63d34c74f8cd0e39eb0eab2219c Mon Sep 17 00:00:00 2001 From: whitequark Date: Thu, 2 Jul 2015 18:19:01 +0300 Subject: [PATCH 061/369] Initialize types.TBuiltin's attributes field. --- artiq/compiler/types.py | 1 + 1 file changed, 1 insertion(+) diff --git a/artiq/compiler/types.py b/artiq/compiler/types.py index 388b1d6ff..41b3733fa 100644 --- a/artiq/compiler/types.py +++ b/artiq/compiler/types.py @@ -191,6 +191,7 @@ class TBuiltin(Type): def __init__(self, name): self.name = name + self.attributes = {} def find(self): return self From 1702251ee56b913dd8e138fae4c9db84c425997c Mon Sep 17 00:00:00 2001 From: whitequark Date: Thu, 2 Jul 2015 18:44:09 +0300 Subject: [PATCH 062/369] Add region field to types. --- artiq/compiler/types.py | 18 +++++++++++++----- artiq/compiler/typing.py | 2 +- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/artiq/compiler/types.py b/artiq/compiler/types.py index 41b3733fa..4db3fa2be 100644 --- a/artiq/compiler/types.py +++ b/artiq/compiler/types.py @@ -67,12 +67,17 @@ class TVar(Type): # any lookups or comparisons. class TMono(Type): - """A monomorphic type, possibly parametric.""" + """ + A monomorphic type, possibly parametric. + + :class:`TMono` is supposed to be subclassed by builtin types, + unlike all other :class:`Type` descendants. + """ attributes = {} - def __init__(self, name, params={}): - self.name, self.params = name, params + def __init__(self, name, params={}, region=None): + self.name, self.params, self.region = name, params, region def find(self): return self @@ -109,6 +114,7 @@ class TTuple(Type): """ attributes = {} + region = None def __init__(self, elts=[]): self.elts = elts @@ -149,8 +155,8 @@ class TFunction(Type): attributes = {} - def __init__(self, args, optargs, ret): - self.args, self.optargs, self.ret = args, optargs, ret + def __init__(self, args, optargs, ret, region=None): + self.args, self.optargs, self.ret, self.region = args, optargs, ret, region def arity(self): return len(self.args) + len(self.optargs) @@ -189,6 +195,8 @@ class TBuiltin(Type): type is treated specially according to its name. """ + region = None + def __init__(self, name): self.name = name self.attributes = {} diff --git a/artiq/compiler/typing.py b/artiq/compiler/typing.py index 3c8c62f76..c7ae71327 100644 --- a/artiq/compiler/typing.py +++ b/artiq/compiler/typing.py @@ -444,7 +444,7 @@ class Inferencer(algorithm.Visitor): def visit_ListT(self, node): self.generic_visit(node) for elt in node.elts: - self._unify(node.type["elt"], elt.type, + self._unify(node.type, builtins.TList(elt.type), node.loc, elt.loc, self._makenotes_elts(node.elts, "a list element")) def visit_AttributeT(self, node): From 7ce9bdf54d24052f95ac49ebc9ba10f5319dd1a2 Mon Sep 17 00:00:00 2001 From: whitequark Date: Thu, 2 Jul 2015 19:35:35 +0300 Subject: [PATCH 063/369] Move transforms to artiq.compiler.transforms, add artiq.Module. --- artiq/compiler/__init__.py | 1 + artiq/compiler/module.py | 33 ++ artiq/compiler/testbench/__init__.py | 0 artiq/compiler/testbench/inferencer.py | 75 +++ artiq/compiler/transforms/__init__.py | 2 + .../compiler/transforms/asttyped_rewriter.py | 377 +++++++++++++++ .../{typing.py => transforms/inferencer.py} | 452 +----------------- .../{typing => inferencer}/builtin_calls.py | 2 +- .../compiler/{typing => inferencer}/coerce.py | 2 +- .../error_builtin_calls.py | 2 +- .../{typing => inferencer}/error_call.py | 2 +- .../{typing => inferencer}/error_coerce.py | 2 +- .../error_control_flow.py | 2 +- .../{typing => inferencer}/error_exception.py | 2 +- .../{typing => inferencer}/error_iterable.py | 2 +- .../error_local_unbound.py | 2 +- .../{typing => inferencer}/error_locals.py | 2 +- .../{typing => inferencer}/error_return.py | 2 +- .../{typing => inferencer}/error_unify.py | 2 +- .../{typing => inferencer}/exception.py | 2 +- .../compiler/{typing => inferencer}/gcd.py | 2 +- .../{typing => inferencer}/prelude.py | 2 +- .../{typing => inferencer}/scoping.py | 2 +- .../compiler/{typing => inferencer}/unify.py | 2 +- 24 files changed, 508 insertions(+), 466 deletions(-) create mode 100644 artiq/compiler/module.py create mode 100644 artiq/compiler/testbench/__init__.py create mode 100644 artiq/compiler/testbench/inferencer.py create mode 100644 artiq/compiler/transforms/__init__.py create mode 100644 artiq/compiler/transforms/asttyped_rewriter.py rename artiq/compiler/{typing.py => transforms/inferencer.py} (68%) rename lit-test/compiler/{typing => inferencer}/builtin_calls.py (92%) rename lit-test/compiler/{typing => inferencer}/coerce.py (95%) rename lit-test/compiler/{typing => inferencer}/error_builtin_calls.py (86%) rename lit-test/compiler/{typing => inferencer}/error_call.py (86%) rename lit-test/compiler/{typing => inferencer}/error_coerce.py (96%) rename lit-test/compiler/{typing => inferencer}/error_control_flow.py (87%) rename lit-test/compiler/{typing => inferencer}/error_exception.py (80%) rename lit-test/compiler/{typing => inferencer}/error_iterable.py (64%) rename lit-test/compiler/{typing => inferencer}/error_local_unbound.py (61%) rename lit-test/compiler/{typing => inferencer}/error_locals.py (93%) rename lit-test/compiler/{typing => inferencer}/error_return.py (88%) rename lit-test/compiler/{typing => inferencer}/error_unify.py (92%) rename lit-test/compiler/{typing => inferencer}/exception.py (76%) rename lit-test/compiler/{typing => inferencer}/gcd.py (80%) rename lit-test/compiler/{typing => inferencer}/prelude.py (71%) rename lit-test/compiler/{typing => inferencer}/scoping.py (63%) rename lit-test/compiler/{typing => inferencer}/unify.py (94%) diff --git a/artiq/compiler/__init__.py b/artiq/compiler/__init__.py index e69de29bb..8f210ac92 100644 --- a/artiq/compiler/__init__.py +++ b/artiq/compiler/__init__.py @@ -0,0 +1 @@ +from .module import Module diff --git a/artiq/compiler/module.py b/artiq/compiler/module.py new file mode 100644 index 000000000..82b1ef455 --- /dev/null +++ b/artiq/compiler/module.py @@ -0,0 +1,33 @@ +""" +The :class:`Module` class encapsulates a single Python +""" + +import os +from pythonparser import source, diagnostic, parse_buffer +from . import prelude, types, transforms + +class Module: + def __init__(self, source_buffer, engine=diagnostic.Engine(all_errors_are_fatal=True)): + parsetree, comments = parse_buffer(source_buffer, engine=engine) + self.name = os.path.basename(source_buffer.name) + + asttyped_rewriter = transforms.ASTTypedRewriter(engine=engine) + typedtree = asttyped_rewriter.visit(parsetree) + self.globals = asttyped_rewriter.globals + + inferencer = transforms.Inferencer(engine=engine) + inferencer.visit(typedtree) + + @classmethod + def from_string(klass, source_string, name="input.py", first_line=1): + return klass(source.Buffer(source_string + "\n", name, first_line)) + + @classmethod + def from_filename(klass, filename): + with open(filename) as f: + return klass(source.Buffer(f.read(), filename, 1)) + + 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/testbench/__init__.py b/artiq/compiler/testbench/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/artiq/compiler/testbench/inferencer.py b/artiq/compiler/testbench/inferencer.py new file mode 100644 index 000000000..b330bf055 --- /dev/null +++ b/artiq/compiler/testbench/inferencer.py @@ -0,0 +1,75 @@ +import sys, fileinput, os +from pythonparser import source, diagnostic, algorithm, parse_buffer +from .. import prelude, types +from ..transforms import ASTTypedRewriter, Inferencer + +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 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] == '+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(globals=prelude.globals(), engine=engine).visit(parsed) + Inferencer(engine=engine).visit(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/transforms/__init__.py b/artiq/compiler/transforms/__init__.py new file mode 100644 index 000000000..749508a5a --- /dev/null +++ b/artiq/compiler/transforms/__init__.py @@ -0,0 +1,2 @@ +from .asttyped_rewriter import ASTTypedRewriter +from .inferencer import Inferencer diff --git a/artiq/compiler/transforms/asttyped_rewriter.py b/artiq/compiler/transforms/asttyped_rewriter.py new file mode 100644 index 000000000..065c05ee0 --- /dev/null +++ b/artiq/compiler/transforms/asttyped_rewriter.py @@ -0,0 +1,377 @@ +from pythonparser import algorithm, diagnostic +from .. import asttyped, types, builtins, prelude + +# 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 = {} + + # 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): + try: + self.in_assign = True + return self.visit(node) + finally: + self.in_assign = False + + def visit_Assign(self, node): + self.visit(node.value) + for target in node.targets: + self.visit_in_assign(target) + + def visit_For(self, node): + self.visit(node.iter) + self.visit_in_assign(node.target) + self.visit(node.body) + self.visit(node.orelse) + + def visit_withitem(self, node): + self.visit(node.context_expr) + if node.optional_vars is not None: + self.visit_in_assign(node.optional_vars) + + def visit_comprehension(self, node): + self.visit(node.iter) + self.visit_in_assign(node.target) + for if_ in node.ifs: + self.visit(node.ifs) + + 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 + visit_DictComp = visit_root + visit_ListComp = visit_root + visit_SetComp = visit_root + visit_GeneratorExp = visit_root + + def visit_FunctionDef(self, node): + if self.in_root: + self._assignable(node.name) + self.visit_root(node) + + def _assignable(self, name): + 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 _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) + self._assignable(name) + self.env_stack[1][name] = self.typing_env[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) + 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): + self.engine = engine + self.globals = None + self.env_stack = [prelude.globals()] + + def _find_name(self, name, loc): + for typing_env in reversed(self.env_stack): + if name in typing_env: + return typing_env[name] + diag = diagnostic.Diagnostic("fatal", + "name '{name}' is not bound to anything", {"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) + + node = asttyped.FunctionDefT( + typing_env=extractor.typing_env, globals_in_scope=extractor.global_, + signature_type=self._find_name(node.name, node.name_loc), 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_arg(self, node): + return asttyped.argT(type=self._find_name(node.arg, node.loc), + arg=node.arg, annotation=self.visit(node.annotation), + 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_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, 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_Subscript(self, node): + node = self.generic_visit(node) + node = asttyped.SubscriptT(type=types.TVar(), + value=node.value, slice=node.slice, ctx=node.ctx, + 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(), + 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) + node = asttyped.ExceptHandlerT( + name_type=self._find_name(node.name, node.name_loc), + 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 + + # 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_Str = visit_unsupported + visit_Starred = visit_unsupported + visit_Yield = visit_unsupported + visit_YieldFrom = visit_unsupported + + # stmt + visit_Assert = visit_unsupported + visit_ClassDef = visit_unsupported + visit_Delete = visit_unsupported + visit_Import = visit_unsupported + visit_ImportFrom = visit_unsupported diff --git a/artiq/compiler/typing.py b/artiq/compiler/transforms/inferencer.py similarity index 68% rename from artiq/compiler/typing.py rename to artiq/compiler/transforms/inferencer.py index c7ae71327..f78acea22 100644 --- a/artiq/compiler/typing.py +++ b/artiq/compiler/transforms/inferencer.py @@ -1,378 +1,6 @@ -from pythonparser import source, ast, algorithm, diagnostic, parse_buffer from collections import OrderedDict -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 = {} - - # 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() - - if len(self.env_stack) == 1: - self.env_stack.append(self.typing_env) - - def visit_in_assign(self, node): - try: - self.in_assign = True - return self.visit(node) - finally: - self.in_assign = False - - def visit_Assign(self, node): - self.visit(node.value) - for target in node.targets: - self.visit_in_assign(target) - - def visit_For(self, node): - self.visit(node.iter) - self.visit_in_assign(node.target) - self.visit(node.body) - self.visit(node.orelse) - - def visit_withitem(self, node): - self.visit(node.context_expr) - if node.optional_vars is not None: - self.visit_in_assign(node.optional_vars) - - def visit_comprehension(self, node): - self.visit(node.iter) - self.visit_in_assign(node.target) - for if_ in node.ifs: - self.visit(node.ifs) - - 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 - visit_DictComp = visit_root - visit_ListComp = visit_root - visit_SetComp = visit_root - visit_GeneratorExp = visit_root - - def visit_FunctionDef(self, node): - if self.in_root: - self._assignable(node.name) - self.visit_root(node) - - def _assignable(self, name): - 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 _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) - self._assignable(name) - self.env_stack[1][name] = self.typing_env[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) - 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, globals={}): - self.engine = engine - self.env_stack = [globals] - - def _find_name(self, name, loc): - for typing_env in reversed(self.env_stack): - if name in typing_env: - return typing_env[name] - diag = diagnostic.Diagnostic("fatal", - "name '{name}' is not bound to anything", {"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) - return self.generic_visit(node) - - def visit_FunctionDef(self, node): - extractor = LocalExtractor(env_stack=self.env_stack, engine=self.engine) - extractor.visit(node) - - node = asttyped.FunctionDefT( - typing_env=extractor.typing_env, globals_in_scope=extractor.global_, - signature_type=self._find_name(node.name, node.name_loc), 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_arg(self, node): - return asttyped.argT(type=self._find_name(node.arg, node.loc), - arg=node.arg, annotation=self.visit(node.annotation), - 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_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, 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_Subscript(self, node): - node = self.generic_visit(node) - node = asttyped.SubscriptT(type=types.TVar(), - value=node.value, slice=node.slice, ctx=node.ctx, - 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(), - 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) - node = asttyped.ExceptHandlerT( - name_type=self._find_name(node.name, node.name_loc), - 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 - - # 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_Str = visit_unsupported - visit_Starred = visit_unsupported - visit_Yield = visit_unsupported - visit_YieldFrom = visit_unsupported - - # stmt - visit_Assert = visit_unsupported - visit_ClassDef = visit_unsupported - visit_Delete = visit_unsupported - visit_Import = visit_unsupported - visit_ImportFrom = visit_unsupported - +from pythonparser import algorithm, diagnostic, ast +from .. import asttyped, types, builtins class Inferencer(algorithm.Visitor): """ @@ -444,7 +72,7 @@ class Inferencer(algorithm.Visitor): def visit_ListT(self, node): self.generic_visit(node) for elt in node.elts: - self._unify(node.type, builtins.TList(elt.type), + self._unify(node.type["elt"], elt.type, node.loc, elt.loc, self._makenotes_elts(node.elts, "a list element")) def visit_AttributeT(self, node): @@ -1173,77 +801,3 @@ class Inferencer(algorithm.Visitor): else: self._unify(self.function.return_type, node.value.type, self.function.name_loc, node.value.loc, makenotes) - -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 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(): - import sys, fileinput, os - from . import prelude - - 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(globals=prelude.globals(), engine=engine).visit(parsed) - Inferencer(engine=engine).visit(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/lit-test/compiler/typing/builtin_calls.py b/lit-test/compiler/inferencer/builtin_calls.py similarity index 92% rename from lit-test/compiler/typing/builtin_calls.py rename to lit-test/compiler/inferencer/builtin_calls.py index 82c750bf1..50597151f 100644 --- a/lit-test/compiler/typing/builtin_calls.py +++ b/lit-test/compiler/inferencer/builtin_calls.py @@ -1,4 +1,4 @@ -# RUN: %python -m artiq.compiler.typing %s >%t +# RUN: %python -m artiq.compiler.testbench.inferencer %s >%t # RUN: OutputCheck %s --file-to-check=%t # CHECK-L: bool:():bool diff --git a/lit-test/compiler/typing/coerce.py b/lit-test/compiler/inferencer/coerce.py similarity index 95% rename from lit-test/compiler/typing/coerce.py rename to lit-test/compiler/inferencer/coerce.py index 5b161ab30..3b0190c49 100644 --- a/lit-test/compiler/typing/coerce.py +++ b/lit-test/compiler/inferencer/coerce.py @@ -1,4 +1,4 @@ -# RUN: %python -m artiq.compiler.typing %s >%t +# RUN: %python -m artiq.compiler.testbench.inferencer %s >%t # RUN: OutputCheck %s --file-to-check=%t 1 | 2 diff --git a/lit-test/compiler/typing/error_builtin_calls.py b/lit-test/compiler/inferencer/error_builtin_calls.py similarity index 86% rename from lit-test/compiler/typing/error_builtin_calls.py rename to lit-test/compiler/inferencer/error_builtin_calls.py index 858df6d4a..32ce8b294 100644 --- a/lit-test/compiler/typing/error_builtin_calls.py +++ b/lit-test/compiler/inferencer/error_builtin_calls.py @@ -1,4 +1,4 @@ -# RUN: %python -m artiq.compiler.typing +diag %s >%t +# RUN: %python -m artiq.compiler.testbench.inferencer +diag %s >%t # RUN: OutputCheck %s --file-to-check=%t a = 1 diff --git a/lit-test/compiler/typing/error_call.py b/lit-test/compiler/inferencer/error_call.py similarity index 86% rename from lit-test/compiler/typing/error_call.py rename to lit-test/compiler/inferencer/error_call.py index 2ff169faf..21ba5bdf6 100644 --- a/lit-test/compiler/typing/error_call.py +++ b/lit-test/compiler/inferencer/error_call.py @@ -1,4 +1,4 @@ -# RUN: %python -m artiq.compiler.typing +diag %s >%t +# 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 int diff --git a/lit-test/compiler/typing/error_coerce.py b/lit-test/compiler/inferencer/error_coerce.py similarity index 96% rename from lit-test/compiler/typing/error_coerce.py rename to lit-test/compiler/inferencer/error_coerce.py index feb7447b8..bf4a5dd36 100644 --- a/lit-test/compiler/typing/error_coerce.py +++ b/lit-test/compiler/inferencer/error_coerce.py @@ -1,4 +1,4 @@ -# RUN: %python -m artiq.compiler.typing +diag %s >%t +# 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 diff --git a/lit-test/compiler/typing/error_control_flow.py b/lit-test/compiler/inferencer/error_control_flow.py similarity index 87% rename from lit-test/compiler/typing/error_control_flow.py rename to lit-test/compiler/inferencer/error_control_flow.py index c5a913bdd..65e300511 100644 --- a/lit-test/compiler/typing/error_control_flow.py +++ b/lit-test/compiler/inferencer/error_control_flow.py @@ -1,4 +1,4 @@ -# RUN: %python -m artiq.compiler.typing +diag %s >%t +# 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 diff --git a/lit-test/compiler/typing/error_exception.py b/lit-test/compiler/inferencer/error_exception.py similarity index 80% rename from lit-test/compiler/typing/error_exception.py rename to lit-test/compiler/inferencer/error_exception.py index 4172f7bde..4e4b340b5 100644 --- a/lit-test/compiler/typing/error_exception.py +++ b/lit-test/compiler/inferencer/error_exception.py @@ -1,4 +1,4 @@ -# RUN: %python -m artiq.compiler.typing +diag %s >%t +# RUN: %python -m artiq.compiler.testbench.inferencer +diag %s >%t # RUN: OutputCheck %s --file-to-check=%t try: diff --git a/lit-test/compiler/typing/error_iterable.py b/lit-test/compiler/inferencer/error_iterable.py similarity index 64% rename from lit-test/compiler/typing/error_iterable.py rename to lit-test/compiler/inferencer/error_iterable.py index d3334a616..68ed1b002 100644 --- a/lit-test/compiler/typing/error_iterable.py +++ b/lit-test/compiler/inferencer/error_iterable.py @@ -1,4 +1,4 @@ -# RUN: %python -m artiq.compiler.typing +diag %s >%t +# RUN: %python -m artiq.compiler.testbench.inferencer +diag %s >%t # RUN: OutputCheck %s --file-to-check=%t # CHECK-L: ${LINE:+1}: error: type int(width='a) is not iterable diff --git a/lit-test/compiler/typing/error_local_unbound.py b/lit-test/compiler/inferencer/error_local_unbound.py similarity index 61% rename from lit-test/compiler/typing/error_local_unbound.py rename to lit-test/compiler/inferencer/error_local_unbound.py index fdf1b3047..6b4350157 100644 --- a/lit-test/compiler/typing/error_local_unbound.py +++ b/lit-test/compiler/inferencer/error_local_unbound.py @@ -1,4 +1,4 @@ -# RUN: %python -m artiq.compiler.typing +diag %s >%t +# RUN: %python -m artiq.compiler.testbench.inferencer +diag %s >%t # RUN: OutputCheck %s --file-to-check=%t # CHECK-L: ${LINE:+1}: fatal: name 'x' is not bound to anything diff --git a/lit-test/compiler/typing/error_locals.py b/lit-test/compiler/inferencer/error_locals.py similarity index 93% rename from lit-test/compiler/typing/error_locals.py rename to lit-test/compiler/inferencer/error_locals.py index 048295d2c..3a5185ca8 100644 --- a/lit-test/compiler/typing/error_locals.py +++ b/lit-test/compiler/inferencer/error_locals.py @@ -1,4 +1,4 @@ -# RUN: %python -m artiq.compiler.typing +diag %s >%t +# RUN: %python -m artiq.compiler.testbench.inferencer +diag %s >%t # RUN: OutputCheck %s --file-to-check=%t x = 1 diff --git a/lit-test/compiler/typing/error_return.py b/lit-test/compiler/inferencer/error_return.py similarity index 88% rename from lit-test/compiler/typing/error_return.py rename to lit-test/compiler/inferencer/error_return.py index fff45ed0b..ba4ddc2b3 100644 --- a/lit-test/compiler/typing/error_return.py +++ b/lit-test/compiler/inferencer/error_return.py @@ -1,4 +1,4 @@ -# RUN: %python -m artiq.compiler.typing +diag %s >%t +# RUN: %python -m artiq.compiler.testbench.inferencer +diag %s >%t # RUN: OutputCheck %s --file-to-check=%t # CHECK-L: ${LINE:+2}: error: cannot unify int(width='a) with NoneType diff --git a/lit-test/compiler/typing/error_unify.py b/lit-test/compiler/inferencer/error_unify.py similarity index 92% rename from lit-test/compiler/typing/error_unify.py rename to lit-test/compiler/inferencer/error_unify.py index 076be96f4..e81537d29 100644 --- a/lit-test/compiler/typing/error_unify.py +++ b/lit-test/compiler/inferencer/error_unify.py @@ -1,4 +1,4 @@ -# RUN: %python -m artiq.compiler.typing +diag %s >%t +# RUN: %python -m artiq.compiler.testbench.inferencer +diag %s >%t # RUN: OutputCheck %s --file-to-check=%t a = 1 diff --git a/lit-test/compiler/typing/exception.py b/lit-test/compiler/inferencer/exception.py similarity index 76% rename from lit-test/compiler/typing/exception.py rename to lit-test/compiler/inferencer/exception.py index 32046157f..bfdbbfb7c 100644 --- a/lit-test/compiler/typing/exception.py +++ b/lit-test/compiler/inferencer/exception.py @@ -1,4 +1,4 @@ -# RUN: %python -m artiq.compiler.typing %s >%t +# RUN: %python -m artiq.compiler.testbench.inferencer %s >%t # RUN: OutputCheck %s --file-to-check=%t # CHECK-L: Exception: diff --git a/lit-test/compiler/typing/gcd.py b/lit-test/compiler/inferencer/gcd.py similarity index 80% rename from lit-test/compiler/typing/gcd.py rename to lit-test/compiler/inferencer/gcd.py index c79052d89..e2d4b4779 100644 --- a/lit-test/compiler/typing/gcd.py +++ b/lit-test/compiler/inferencer/gcd.py @@ -1,4 +1,4 @@ -# RUN: %python -m artiq.compiler.typing %s >%t +# RUN: %python -m artiq.compiler.testbench.inferencer %s >%t def _gcd(a, b): if a < 0: diff --git a/lit-test/compiler/typing/prelude.py b/lit-test/compiler/inferencer/prelude.py similarity index 71% rename from lit-test/compiler/typing/prelude.py rename to lit-test/compiler/inferencer/prelude.py index 1b70be299..643895e4a 100644 --- a/lit-test/compiler/typing/prelude.py +++ b/lit-test/compiler/inferencer/prelude.py @@ -1,4 +1,4 @@ -# RUN: %python -m artiq.compiler.typing %s >%t +# RUN: %python -m artiq.compiler.testbench.inferencer %s >%t # RUN: OutputCheck %s --file-to-check=%t # CHECK-L: x: diff --git a/lit-test/compiler/typing/scoping.py b/lit-test/compiler/inferencer/scoping.py similarity index 63% rename from lit-test/compiler/typing/scoping.py rename to lit-test/compiler/inferencer/scoping.py index 1d1da612c..595daecf6 100644 --- a/lit-test/compiler/typing/scoping.py +++ b/lit-test/compiler/inferencer/scoping.py @@ -1,4 +1,4 @@ -# RUN: %python -m artiq.compiler.typing %s >%t +# RUN: %python -m artiq.compiler.testbench.inferencer %s >%t # RUN: OutputCheck %s --file-to-check=%t def f(): diff --git a/lit-test/compiler/typing/unify.py b/lit-test/compiler/inferencer/unify.py similarity index 94% rename from lit-test/compiler/typing/unify.py rename to lit-test/compiler/inferencer/unify.py index 9621c5b59..04c8aecbd 100644 --- a/lit-test/compiler/typing/unify.py +++ b/lit-test/compiler/inferencer/unify.py @@ -1,4 +1,4 @@ -# RUN: %python -m artiq.compiler.typing %s >%t +# RUN: %python -m artiq.compiler.testbench.inferencer %s >%t # RUN: OutputCheck %s --file-to-check=%t a = 1 From 196acb37f640017e95425d808014eb5b289f734e Mon Sep 17 00:00:00 2001 From: whitequark Date: Thu, 2 Jul 2015 19:49:52 +0300 Subject: [PATCH 064/369] Add IntMonomorphizer. --- artiq/compiler/module.py | 15 ++++++---- artiq/compiler/transforms/__init__.py | 1 + .../compiler/transforms/asttyped_rewriter.py | 5 ++++ artiq/compiler/transforms/inferencer.py | 7 ++++- .../compiler/transforms/int_monomorphizer.py | 28 +++++++++++++++++++ artiq/compiler/types.py | 4 +-- 6 files changed, 51 insertions(+), 9 deletions(-) create mode 100644 artiq/compiler/transforms/int_monomorphizer.py diff --git a/artiq/compiler/module.py b/artiq/compiler/module.py index 82b1ef455..e3e8280f0 100644 --- a/artiq/compiler/module.py +++ b/artiq/compiler/module.py @@ -8,15 +8,18 @@ from . import prelude, types, transforms class Module: def __init__(self, source_buffer, engine=diagnostic.Engine(all_errors_are_fatal=True)): - parsetree, comments = parse_buffer(source_buffer, engine=engine) - self.name = os.path.basename(source_buffer.name) - asttyped_rewriter = transforms.ASTTypedRewriter(engine=engine) - typedtree = asttyped_rewriter.visit(parsetree) - self.globals = asttyped_rewriter.globals - inferencer = transforms.Inferencer(engine=engine) + int_monomorphizer = transforms.IntMonomorphizer(engine=engine) + + parsetree, comments = parse_buffer(source_buffer, engine=engine) + typedtree = asttyped_rewriter.visit(parsetree) inferencer.visit(typedtree) + int_monomorphizer.visit(typedtree) + inferencer.visit(typedtree) + + self.name = os.path.basename(source_buffer.name) + self.globals = asttyped_rewriter.globals @classmethod def from_string(klass, source_string, name="input.py", first_line=1): diff --git a/artiq/compiler/transforms/__init__.py b/artiq/compiler/transforms/__init__.py index 749508a5a..795c14c79 100644 --- a/artiq/compiler/transforms/__init__.py +++ b/artiq/compiler/transforms/__init__.py @@ -1,2 +1,3 @@ from .asttyped_rewriter import ASTTypedRewriter from .inferencer import Inferencer +from .int_monomorphizer import IntMonomorphizer diff --git a/artiq/compiler/transforms/asttyped_rewriter.py b/artiq/compiler/transforms/asttyped_rewriter.py index 065c05ee0..13ba60361 100644 --- a/artiq/compiler/transforms/asttyped_rewriter.py +++ b/artiq/compiler/transforms/asttyped_rewriter.py @@ -1,3 +1,8 @@ +""" +:class:`ASTTypedRewriter` rewrites a parsetree (:mod:`pythonparser.ast`) +to a typedtree (:mod:`..asttyped`). +""" + from pythonparser import algorithm, diagnostic from .. import asttyped, types, builtins, prelude diff --git a/artiq/compiler/transforms/inferencer.py b/artiq/compiler/transforms/inferencer.py index f78acea22..f4aa14d5e 100644 --- a/artiq/compiler/transforms/inferencer.py +++ b/artiq/compiler/transforms/inferencer.py @@ -1,3 +1,7 @@ +""" +:class:`Inferencer` performs unification-based inference on a typedtree. +""" + from collections import OrderedDict from pythonparser import algorithm, diagnostic, ast from .. import asttyped, types, builtins @@ -168,6 +172,7 @@ class Inferencer(algorithm.Visitor): if coerced_node.type.find() == typ.find(): return coerced_node elif isinstance(coerced_node, asttyped.CoerceT): + node = coerced_node node.type, node.other_expr = typ, other_node else: node = asttyped.CoerceT(type=typ, expr=coerced_node, other_expr=other_node, @@ -191,7 +196,7 @@ class Inferencer(algorithm.Visitor): elif any(map(builtins.is_float, node_types)): typ = builtins.TFloat() elif any(map(builtins.is_int, node_types)): - widths = map(builtins.get_int_width, node_types) + widths = list(map(builtins.get_int_width, node_types)) if all(widths): typ = builtins.TInt(types.TValue(max(widths))) else: diff --git a/artiq/compiler/transforms/int_monomorphizer.py b/artiq/compiler/transforms/int_monomorphizer.py new file mode 100644 index 000000000..3aeed9140 --- /dev/null +++ b/artiq/compiler/transforms/int_monomorphizer.py @@ -0,0 +1,28 @@ +""" +: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 + +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/types.py b/artiq/compiler/types.py index 4db3fa2be..239373768 100644 --- a/artiq/compiler/types.py +++ b/artiq/compiler/types.py @@ -168,8 +168,8 @@ class TFunction(Type): if isinstance(other, TFunction) and \ self.args.keys() == other.args.keys() and \ self.optargs.keys() == other.optargs.keys(): - for selfarg, otherarg in zip(self.args.values() + self.optargs.values(), - other.args.values() + other.optargs.values()): + 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) elif isinstance(other, TVar): From 8a65266f14e4841562bc18e91848d6cf7f93752d Mon Sep 17 00:00:00 2001 From: whitequark Date: Thu, 2 Jul 2015 20:06:07 +0300 Subject: [PATCH 065/369] Improve builtin call error message. --- artiq/compiler/transforms/inferencer.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/artiq/compiler/transforms/inferencer.py b/artiq/compiler/transforms/inferencer.py index f4aa14d5e..0eac54604 100644 --- a/artiq/compiler/transforms/inferencer.py +++ b/artiq/compiler/transforms/inferencer.py @@ -374,9 +374,13 @@ class Inferencer(algorithm.Visitor): 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 these arguments", - {"func": typ.name}, + "{func} cannot be invoked with the arguments ({args})", + {"func": typ.name, "args": ", ".join(args)}, node.func.loc, notes=valid_forms) self.engine.process(diag) From 73a8f3c442dcb9f1629563a09bac4800fc9a732b Mon Sep 17 00:00:00 2001 From: whitequark Date: Thu, 2 Jul 2015 20:06:43 +0300 Subject: [PATCH 066/369] Fix tests. --- artiq/compiler/testbench/inferencer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/artiq/compiler/testbench/inferencer.py b/artiq/compiler/testbench/inferencer.py index b330bf055..29b8c86fc 100644 --- a/artiq/compiler/testbench/inferencer.py +++ b/artiq/compiler/testbench/inferencer.py @@ -60,7 +60,7 @@ def main(): buf = source.Buffer("".join(fileinput.input()).expandtabs(), os.path.basename(fileinput.filename())) parsed, comments = parse_buffer(buf, engine=engine) - typed = ASTTypedRewriter(globals=prelude.globals(), engine=engine).visit(parsed) + typed = ASTTypedRewriter(engine=engine).visit(parsed) Inferencer(engine=engine).visit(typed) printer = Printer(buf) From 02b41ea0f7b20107707d940afd2a5b23ed6c89f1 Mon Sep 17 00:00:00 2001 From: whitequark Date: Thu, 2 Jul 2015 21:28:26 +0300 Subject: [PATCH 067/369] Add MonomorphismChecker. --- artiq/compiler/asttyped.py | 2 +- artiq/compiler/module.py | 15 ++++--- artiq/compiler/testbench/inferencer.py | 1 - artiq/compiler/testbench/module.py | 25 ++++++++++++ artiq/compiler/transforms/__init__.py | 1 + .../transforms/monomorphism_checker.py | 39 +++++++++++++++++++ artiq/compiler/types.py | 33 ++++++++++++++++ .../compiler/monomorphism/error_notmono.py | 9 +++++ lit-test/compiler/monomorphism/integers.py | 5 +++ 9 files changed, 123 insertions(+), 7 deletions(-) create mode 100644 artiq/compiler/testbench/module.py create mode 100644 artiq/compiler/transforms/monomorphism_checker.py create mode 100644 lit-test/compiler/monomorphism/error_notmono.py create mode 100644 lit-test/compiler/monomorphism/integers.py diff --git a/artiq/compiler/asttyped.py b/artiq/compiler/asttyped.py index c592161e6..5184c7401 100644 --- a/artiq/compiler/asttyped.py +++ b/artiq/compiler/asttyped.py @@ -33,7 +33,7 @@ class FunctionDefT(ast.FunctionDef, scoped): class ModuleT(ast.Module, scoped): pass -class ExceptHandlerT(ast.ExceptHandler, commontyped): +class ExceptHandlerT(ast.ExceptHandler): _fields = ("filter", "name", "body") # rename ast.ExceptHandler.type _types = ("name_type",) diff --git a/artiq/compiler/module.py b/artiq/compiler/module.py index e3e8280f0..15db5b368 100644 --- a/artiq/compiler/module.py +++ b/artiq/compiler/module.py @@ -7,28 +7,33 @@ from pythonparser import source, diagnostic, parse_buffer from . import prelude, types, transforms class Module: - def __init__(self, source_buffer, engine=diagnostic.Engine(all_errors_are_fatal=True)): + def __init__(self, source_buffer, engine=None): + if engine is None: + engine = diagnostic.Engine(all_errors_are_fatal=True) + asttyped_rewriter = transforms.ASTTypedRewriter(engine=engine) inferencer = transforms.Inferencer(engine=engine) int_monomorphizer = transforms.IntMonomorphizer(engine=engine) + monomorphism_checker = transforms.MonomorphismChecker(engine=engine) parsetree, comments = parse_buffer(source_buffer, engine=engine) typedtree = asttyped_rewriter.visit(parsetree) inferencer.visit(typedtree) int_monomorphizer.visit(typedtree) inferencer.visit(typedtree) + monomorphism_checker.visit(typedtree) self.name = os.path.basename(source_buffer.name) self.globals = asttyped_rewriter.globals @classmethod - def from_string(klass, source_string, name="input.py", first_line=1): - return klass(source.Buffer(source_string + "\n", name, first_line)) + def from_string(klass, source_string, name="input.py", first_line=1, engine=None): + return klass(source.Buffer(source_string + "\n", name, first_line), engine=engine) @classmethod - def from_filename(klass, filename): + def from_filename(klass, filename, engine=None): with open(filename) as f: - return klass(source.Buffer(f.read(), filename, 1)) + return klass(source.Buffer(f.read(), filename, 1), engine=engine) def __repr__(self): printer = types.TypePrinter() diff --git a/artiq/compiler/testbench/inferencer.py b/artiq/compiler/testbench/inferencer.py index 29b8c86fc..7380270bc 100644 --- a/artiq/compiler/testbench/inferencer.py +++ b/artiq/compiler/testbench/inferencer.py @@ -70,6 +70,5 @@ def main(): printer.rewriter.remove(comment.loc) print(printer.rewrite().source) - if __name__ == "__main__": main() diff --git a/artiq/compiler/testbench/module.py b/artiq/compiler/testbench/module.py new file mode 100644 index 000000000..681d90d7c --- /dev/null +++ b/artiq/compiler/testbench/module.py @@ -0,0 +1,25 @@ +import sys, fileinput +from pythonparser import diagnostic +from .. import Module + +def main(): + 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 + + mod = Module.from_string("".join(fileinput.input()).expandtabs(), engine=engine) + print(repr(mod)) + +if __name__ == "__main__": + main() diff --git a/artiq/compiler/transforms/__init__.py b/artiq/compiler/transforms/__init__.py index 795c14c79..ed45a42f8 100644 --- a/artiq/compiler/transforms/__init__.py +++ b/artiq/compiler/transforms/__init__.py @@ -1,3 +1,4 @@ from .asttyped_rewriter import ASTTypedRewriter from .inferencer import Inferencer from .int_monomorphizer import IntMonomorphizer +from .monomorphism_checker import MonomorphismChecker diff --git a/artiq/compiler/transforms/monomorphism_checker.py b/artiq/compiler/transforms/monomorphism_checker.py new file mode 100644 index 000000000..b777c9082 --- /dev/null +++ b/artiq/compiler/transforms/monomorphism_checker.py @@ -0,0 +1,39 @@ +""" +:class:`MonomorphismChecker` 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 MonomorphismChecker(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) + + 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/compiler/types.py b/artiq/compiler/types.py index 239373768..47d6f055b 100644 --- a/artiq/compiler/types.py +++ b/artiq/compiler/types.py @@ -56,6 +56,12 @@ class TVar(Type): else: self.find().unify(other) + 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 self.parent is self: return "" % id(self) @@ -92,6 +98,11 @@ class TMono(Type): 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): return "py2llvm.types.TMono(%s, %s)" % (repr(self.name), repr(self.params)) @@ -131,6 +142,11 @@ class TTuple(Type): 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): return "py2llvm.types.TTuple(%s)" % repr(self.elts) @@ -177,6 +193,14 @@ class TFunction(Type): else: raise UnificationError(self, other) + def fold(self, accum, fn): + for arg in self.args: + accum = 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): return "py2llvm.types.TFunction(%s, %s, %s)" % \ (repr(self.args), repr(self.optargs), repr(self.ret)) @@ -208,6 +232,9 @@ class TBuiltin(Type): if self != other: raise UnificationError(self, other) + def fold(self, accum, fn): + return fn(accum, self) + def __repr__(self): return "py2llvm.types.TBuiltin(%s)" % repr(self.name) @@ -258,6 +285,9 @@ class TValue(Type): elif self != other: raise UnificationError(self, other) + def fold(self, accum, fn): + return fn(accum, self) + def __repr__(self): return "py2llvm.types.TValue(%s)" % repr(self.value) @@ -281,6 +311,9 @@ def is_mono(typ, name=None, **params): return isinstance(typ, TMono) and \ (name is None or (typ.name == name and params_match)) +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: diff --git a/lit-test/compiler/monomorphism/error_notmono.py b/lit-test/compiler/monomorphism/error_notmono.py new file mode 100644 index 000000000..cf94db697 --- /dev/null +++ b/lit-test/compiler/monomorphism/error_notmono.py @@ -0,0 +1,9 @@ +# RUN: %python -m artiq.compiler.testbench.module +diag %s >%t +# RUN: OutputCheck %s --file-to-check=%t + +# CHECK-L: ${LINE:+1}: error: the type of this expression cannot be fully inferred +x = int(1) + +# CHECK-L: ${LINE:+1}: error: the return type of this function cannot be fully inferred +def fn(): + return int(1) diff --git a/lit-test/compiler/monomorphism/integers.py b/lit-test/compiler/monomorphism/integers.py new file mode 100644 index 000000000..9e6ba2884 --- /dev/null +++ b/lit-test/compiler/monomorphism/integers.py @@ -0,0 +1,5 @@ +# RUN: %python -m artiq.compiler.testbench.module %s >%t +# RUN: OutputCheck %s --file-to-check=%t + +x = 1 +# CHECK-L: x: int(width=32) From 7c833f07270bac6c02cab16a65e17c2a9bcd810a Mon Sep 17 00:00:00 2001 From: whitequark Date: Thu, 2 Jul 2015 21:54:31 +0300 Subject: [PATCH 068/369] Move transforms.MonomorphismChecker to validators.MonomorphismValidator. --- artiq/compiler/module.py | 6 +++--- artiq/compiler/transforms/__init__.py | 1 - artiq/compiler/validators/__init__.py | 1 + .../monomorphism_checker.py => validators/monomorphism.py} | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) create mode 100644 artiq/compiler/validators/__init__.py rename artiq/compiler/{transforms/monomorphism_checker.py => validators/monomorphism.py} (91%) diff --git a/artiq/compiler/module.py b/artiq/compiler/module.py index 15db5b368..7b5e67bf5 100644 --- a/artiq/compiler/module.py +++ b/artiq/compiler/module.py @@ -4,7 +4,7 @@ The :class:`Module` class encapsulates a single Python import os from pythonparser import source, diagnostic, parse_buffer -from . import prelude, types, transforms +from . import prelude, types, transforms, validators class Module: def __init__(self, source_buffer, engine=None): @@ -14,14 +14,14 @@ class Module: asttyped_rewriter = transforms.ASTTypedRewriter(engine=engine) inferencer = transforms.Inferencer(engine=engine) int_monomorphizer = transforms.IntMonomorphizer(engine=engine) - monomorphism_checker = transforms.MonomorphismChecker(engine=engine) + monomorphism_validator = validators.MonomorphismValidator(engine=engine) parsetree, comments = parse_buffer(source_buffer, engine=engine) typedtree = asttyped_rewriter.visit(parsetree) inferencer.visit(typedtree) int_monomorphizer.visit(typedtree) inferencer.visit(typedtree) - monomorphism_checker.visit(typedtree) + monomorphism_validator.visit(typedtree) self.name = os.path.basename(source_buffer.name) self.globals = asttyped_rewriter.globals diff --git a/artiq/compiler/transforms/__init__.py b/artiq/compiler/transforms/__init__.py index ed45a42f8..795c14c79 100644 --- a/artiq/compiler/transforms/__init__.py +++ b/artiq/compiler/transforms/__init__.py @@ -1,4 +1,3 @@ from .asttyped_rewriter import ASTTypedRewriter from .inferencer import Inferencer from .int_monomorphizer import IntMonomorphizer -from .monomorphism_checker import MonomorphismChecker diff --git a/artiq/compiler/validators/__init__.py b/artiq/compiler/validators/__init__.py new file mode 100644 index 000000000..d2ec51350 --- /dev/null +++ b/artiq/compiler/validators/__init__.py @@ -0,0 +1 @@ +from .monomorphism import MonomorphismValidator diff --git a/artiq/compiler/transforms/monomorphism_checker.py b/artiq/compiler/validators/monomorphism.py similarity index 91% rename from artiq/compiler/transforms/monomorphism_checker.py rename to artiq/compiler/validators/monomorphism.py index b777c9082..e4dd1d853 100644 --- a/artiq/compiler/transforms/monomorphism_checker.py +++ b/artiq/compiler/validators/monomorphism.py @@ -1,12 +1,12 @@ """ -:class:`MonomorphismChecker` verifies that all type variables have been +: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 MonomorphismChecker(algorithm.Visitor): +class MonomorphismValidator(algorithm.Visitor): def __init__(self, engine): self.engine = engine From 0ae13ac1b9e80cb5f8055b7884511264768ca7e3 Mon Sep 17 00:00:00 2001 From: whitequark Date: Thu, 2 Jul 2015 22:38:55 +0300 Subject: [PATCH 069/369] Style fixes. --- artiq/compiler/module.py | 8 ++++---- artiq/compiler/testbench/inferencer.py | 6 +++--- artiq/compiler/testbench/module.py | 6 +++--- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/artiq/compiler/module.py b/artiq/compiler/module.py index 7b5e67bf5..d5d21391b 100644 --- a/artiq/compiler/module.py +++ b/artiq/compiler/module.py @@ -27,13 +27,13 @@ class Module: self.globals = asttyped_rewriter.globals @classmethod - def from_string(klass, source_string, name="input.py", first_line=1, engine=None): - return klass(source.Buffer(source_string + "\n", name, first_line), engine=engine) + 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(klass, filename, engine=None): + def from_filename(cls, filename, engine=None): with open(filename) as f: - return klass(source.Buffer(f.read(), filename, 1), engine=engine) + return cls(source.Buffer(f.read(), filename, 1), engine=engine) def __repr__(self): printer = types.TypePrinter() diff --git a/artiq/compiler/testbench/inferencer.py b/artiq/compiler/testbench/inferencer.py index 7380270bc..eda38c90e 100644 --- a/artiq/compiler/testbench/inferencer.py +++ b/artiq/compiler/testbench/inferencer.py @@ -42,16 +42,16 @@ class Printer(algorithm.Visitor): ":{}".format(self.type_printer.name(node.type))) def main(): - if len(sys.argv) > 1 and sys.argv[1] == '+diag': + 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': + if diag.level == "fatal": exit() else: def process_diagnostic(diag): print("\n".join(diag.render())) - if diag.level in ('fatal', 'error'): + if diag.level in ("fatal", "error"): exit(1) engine = diagnostic.Engine() diff --git a/artiq/compiler/testbench/module.py b/artiq/compiler/testbench/module.py index 681d90d7c..b5eab3861 100644 --- a/artiq/compiler/testbench/module.py +++ b/artiq/compiler/testbench/module.py @@ -3,16 +3,16 @@ from pythonparser import diagnostic from .. import Module def main(): - if len(sys.argv) > 1 and sys.argv[1] == '+diag': + 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': + if diag.level == "fatal": exit() else: def process_diagnostic(diag): print("\n".join(diag.render())) - if diag.level in ('fatal', 'error'): + if diag.level in ("fatal", "error"): exit(1) engine = diagnostic.Engine() From bfabca494bd1d3dc0ddd163f41229f1054de8f49 Mon Sep 17 00:00:00 2001 From: whitequark Date: Thu, 2 Jul 2015 22:55:08 +0300 Subject: [PATCH 070/369] Remove regions from types. Unification-based inference for regions is useful with a language that has let bindings (which would propagate the regions) and functions polymorphic over regions. For reasons of simplicity, ARTIQ has neither, and making unification-based inference work would essentially mean adding region coercions between most AST nodes, and having every source subexpression have its own region variable, with the appropriate subtyping relationship. It's simpler to just keep that state outside of typedtree. --- artiq/compiler/module.py | 2 ++ artiq/compiler/types.py | 11 ++++------- artiq/compiler/validators/escape.py | 10 ++++++++++ 3 files changed, 16 insertions(+), 7 deletions(-) create mode 100644 artiq/compiler/validators/escape.py diff --git a/artiq/compiler/module.py b/artiq/compiler/module.py index d5d21391b..2b847eb27 100644 --- a/artiq/compiler/module.py +++ b/artiq/compiler/module.py @@ -15,6 +15,7 @@ class Module: inferencer = transforms.Inferencer(engine=engine) int_monomorphizer = transforms.IntMonomorphizer(engine=engine) monomorphism_validator = validators.MonomorphismValidator(engine=engine) + escape_validator = validators.EscapeValidator(engine=engine) parsetree, comments = parse_buffer(source_buffer, engine=engine) typedtree = asttyped_rewriter.visit(parsetree) @@ -22,6 +23,7 @@ class Module: int_monomorphizer.visit(typedtree) inferencer.visit(typedtree) monomorphism_validator.visit(typedtree) + escape_validator.visit(typedtree) self.name = os.path.basename(source_buffer.name) self.globals = asttyped_rewriter.globals diff --git a/artiq/compiler/types.py b/artiq/compiler/types.py index 47d6f055b..7e8c611f6 100644 --- a/artiq/compiler/types.py +++ b/artiq/compiler/types.py @@ -82,8 +82,8 @@ class TMono(Type): attributes = {} - def __init__(self, name, params={}, region=None): - self.name, self.params, self.region = name, params, region + def __init__(self, name, params={}): + self.name, self.params = name, params def find(self): return self @@ -125,7 +125,6 @@ class TTuple(Type): """ attributes = {} - region = None def __init__(self, elts=[]): self.elts = elts @@ -171,8 +170,8 @@ class TFunction(Type): attributes = {} - def __init__(self, args, optargs, ret, region=None): - self.args, self.optargs, self.ret, self.region = args, optargs, ret, region + def __init__(self, args, optargs, ret): + self.args, self.optargs, self.ret = args, optargs, ret def arity(self): return len(self.args) + len(self.optargs) @@ -219,8 +218,6 @@ class TBuiltin(Type): type is treated specially according to its name. """ - region = None - def __init__(self, name): self.name = name self.attributes = {} diff --git a/artiq/compiler/validators/escape.py b/artiq/compiler/validators/escape.py new file mode 100644 index 000000000..4f02b1b7c --- /dev/null +++ b/artiq/compiler/validators/escape.py @@ -0,0 +1,10 @@ +""" +:class:`EscapeValidator` verifies that no mutable data escapes +the region of its allocation. +""" + +from pythonparser import algorithm, diagnostic +from .. import asttyped, types, builtins + +class EscapeValidator(algorithm.Visitor): + pass From ee0990cb5e43a75d26ce017782299ed6e49fa1b8 Mon Sep 17 00:00:00 2001 From: whitequark Date: Sat, 4 Jul 2015 00:58:48 +0300 Subject: [PATCH 071/369] Automatically infer return type of NoneType for no return statements. --- artiq/compiler/transforms/inferencer.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/artiq/compiler/transforms/inferencer.py b/artiq/compiler/transforms/inferencer.py index 0eac54604..1e1686fc3 100644 --- a/artiq/compiler/transforms/inferencer.py +++ b/artiq/compiler/transforms/inferencer.py @@ -20,6 +20,7 @@ class Inferencer(algorithm.Visitor): 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): try: @@ -768,9 +769,11 @@ class Inferencer(algorithm.Visitor): def visit_FunctionDefT(self, node): 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) self.function = old_function self.in_loop = old_in_loop + self.has_return = old_has_return if any(node.decorator_list): diag = diagnostic.Diagnostic("error", @@ -779,6 +782,14 @@ class Inferencer(algorithm.Visitor): self.engine.process(diag) return + # 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: + self._unify(node.return_type, builtins.TNone(), + node.name_loc, None) + signature_type = self._type_from_arguments(node.args, node.return_type) if signature_type: self._unify(node.signature_type, signature_type, @@ -792,6 +803,8 @@ class Inferencer(algorithm.Visitor): self.engine.process(diag) return + self.has_return = True + self.generic_visit(node) def makenotes(printer, typea, typeb, loca, locb): return [ From 561d403ddddecdc878fda046b9c11d55ad7751c8 Mon Sep 17 00:00:00 2001 From: whitequark Date: Sat, 4 Jul 2015 00:59:03 +0300 Subject: [PATCH 072/369] Add missing _loc forwarding. --- artiq/compiler/transforms/asttyped_rewriter.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/artiq/compiler/transforms/asttyped_rewriter.py b/artiq/compiler/transforms/asttyped_rewriter.py index 13ba60361..69e39e123 100644 --- a/artiq/compiler/transforms/asttyped_rewriter.py +++ b/artiq/compiler/transforms/asttyped_rewriter.py @@ -243,7 +243,8 @@ class ASTTypedRewriter(algorithm.Transformer): def visit_List(self, node): node = self.generic_visit(node) node = asttyped.ListT(type=builtins.TList(), - elts=node.elts, ctx=node.ctx, loc=node.loc) + 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): From 4358c5c4538c9733d0ea8802ef5c67a090dabc2e Mon Sep 17 00:00:00 2001 From: whitequark Date: Sat, 4 Jul 2015 02:23:55 +0300 Subject: [PATCH 073/369] Unbreak return type inference. --- artiq/compiler/transforms/inferencer.py | 27 ++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/artiq/compiler/transforms/inferencer.py b/artiq/compiler/transforms/inferencer.py index 1e1686fc3..5733ac900 100644 --- a/artiq/compiler/transforms/inferencer.py +++ b/artiq/compiler/transforms/inferencer.py @@ -767,14 +767,6 @@ class Inferencer(algorithm.Visitor): arg.loc, default.loc) def visit_FunctionDefT(self, node): - 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) - self.function = old_function - self.in_loop = old_in_loop - self.has_return = old_has_return - if any(node.decorator_list): diag = diagnostic.Diagnostic("error", "decorators are not supported", {}, @@ -782,13 +774,30 @@ class Inferencer(algorithm.Visitor): self.engine.process(diag) return + 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) + node.name_loc, None, makenotes) + + 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: From 16432d2652a83c26951bb1d6de0c8269a8d73762 Mon Sep 17 00:00:00 2001 From: whitequark Date: Sat, 4 Jul 2015 04:16:37 +0300 Subject: [PATCH 074/369] Implement escape analysis. --- artiq/compiler/builtins.py | 4 + .../compiler/transforms/asttyped_rewriter.py | 21 +- artiq/compiler/transforms/inferencer.py | 1 - artiq/compiler/validators/__init__.py | 1 + artiq/compiler/validators/escape.py | 298 +++++++++++++++++- 5 files changed, 316 insertions(+), 9 deletions(-) diff --git a/artiq/compiler/builtins.py b/artiq/compiler/builtins.py index 9e44e0da7..bbdc6462b 100644 --- a/artiq/compiler/builtins.py +++ b/artiq/compiler/builtins.py @@ -132,3 +132,7 @@ def is_exn_constructor(typ, name=None): typ.name == name else: return isinstance(typ, types.TExceptionConstructor) + +def is_mutable(typ): + return typ.fold(False, lambda accum, typ: + is_list(typ) or types.is_function(typ)) diff --git a/artiq/compiler/transforms/asttyped_rewriter.py b/artiq/compiler/transforms/asttyped_rewriter.py index 69e39e123..de72fce3e 100644 --- a/artiq/compiler/transforms/asttyped_rewriter.py +++ b/artiq/compiler/transforms/asttyped_rewriter.py @@ -28,32 +28,32 @@ class LocalExtractor(algorithm.Visitor): # parameters can't be declared as global or nonlocal self.params = set() - def visit_in_assign(self, node): + def visit_in_assign(self, node, in_assign): try: - self.in_assign = True + old_in_assign, self.in_assign = self.in_assign, in_assign return self.visit(node) finally: - self.in_assign = False + self.in_assign = old_in_assign def visit_Assign(self, node): self.visit(node.value) for target in node.targets: - self.visit_in_assign(target) + self.visit_in_assign(target, in_assign=True) def visit_For(self, node): self.visit(node.iter) - self.visit_in_assign(node.target) + 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) if node.optional_vars is not None: - self.visit_in_assign(node.optional_vars) + 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) + self.visit_in_assign(node.target, in_assign=True) for if_ in node.ifs: self.visit(node.ifs) @@ -99,6 +99,13 @@ class LocalExtractor(algorithm.Visitor): # 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", diff --git a/artiq/compiler/transforms/inferencer.py b/artiq/compiler/transforms/inferencer.py index 5733ac900..6694b6129 100644 --- a/artiq/compiler/transforms/inferencer.py +++ b/artiq/compiler/transforms/inferencer.py @@ -350,7 +350,6 @@ class Inferencer(algorithm.Visitor): return types.is_mono(opreand.type) and \ opreand.type.find().name == typ.find().name other_node = next(filter(wide_enough, operands)) - print(typ, other_node) node.left, *node.comparators = \ [self._coerce_one(typ, operand, other_node) for operand in operands] self._unify(node.type, builtins.TBool(), diff --git a/artiq/compiler/validators/__init__.py b/artiq/compiler/validators/__init__.py index d2ec51350..a90a89c69 100644 --- a/artiq/compiler/validators/__init__.py +++ b/artiq/compiler/validators/__init__.py @@ -1 +1,2 @@ from .monomorphism import MonomorphismValidator +from .escape import EscapeValidator diff --git a/artiq/compiler/validators/escape.py b/artiq/compiler/validators/escape.py index 4f02b1b7c..fd030eeef 100644 --- a/artiq/compiler/validators/escape.py +++ b/artiq/compiler/validators/escape.py @@ -3,8 +3,304 @@ the region of its allocation. """ +import functools from pythonparser import algorithm, diagnostic from .. import asttyped, types, builtins +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.source_buffer == other.range.source_buffer + assert self.range + + 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 contract(self, other): + if not self.range: + self.range = other.range + + def outlives(lhs, rhs): + if lhs is None: # lhs lives forever + return True + elif rhs is None: # rhs lives forever, 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 list of regions that must + be alive for the expression to execute. + """ + + 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 None + + assert False + + # Value lives as long as the current scope, if it's mutable, + # or else forever + def visit_BinOpT(self, node): + if builtins.is_mutable(node.type): + return self.youngest_region + else: + return None + + # Value lives as long as the object/container, if it's mutable, + # or else forever + def visit_accessor(self, node): + if builtins.is_mutable(node.type): + return self.visit(node.value) + else: + return None + + 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 None + + 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 + visit_StrT = visit_allocating + + # Value lives forever + def visit_immutable(self, node): + assert not builtins.is_mutable(node.type) + return None + + visit_CompareT = visit_immutable + visit_EllipsisT = visit_immutable + visit_NameConstantT = visit_immutable + visit_NumT = visit_immutable + visit_UnaryOpT = visit_immutable + visit_CallT = visit_immutable + + # 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_NameT(self, node): + return [node] + + def visit_accessor(self, node): + return self.visit(node.value) + + visit_AttributeT = visit_accessor + visit_SubscriptT = visit_accessor + + def visit_sequence(self, node): + return 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): - pass + def __init__(self, engine): + self.engine = engine + self.youngest_region = None + 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 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()) + ] + else: + return [ + diagnostic.Diagnostic("note", + "{descr} is alive forever", {"descr": descr}, + loc) + ] + + def visit_in_region(self, node, region): + try: + old_youngest_region = self.youngest_region + self.youngest_region = region + + old_youngest_env = self.youngest_env + self.youngest_env = {} + + for name in node.typing_env: + if builtins.is_mutable(node.typing_env[name]): + self.youngest_env[name] = Region(None) # not yet known + else: + self.youngest_env[name] = None # lives forever + 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) + + def visit_FunctionDefT(self, node): + self.youngest_env[node.name] = self.youngest_region + self.visit_in_region(node, Region(node.loc)) + + # 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 raising 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. + + def visit_assignment(self, target, value, is_aug_assign=False): + target_region = self._region_of(target) + value_region = self._region_of(value) if not is_aug_assign else self.youngest_region + + # If this is a variable, we might need to contract the live range. + if value_region is not None: + for name in self._names_of(target): + region = self._region_of(name) + if region is not None: + region.contract(value_region) + + # The assigned value should outlive the assignee + if not Region.outlives(value_region, target_region): + if is_aug_assign: + target_desc = "the assignment target, allocated here," + else: + target_desc = "the assignment target" + note = diagnostic.Diagnostic("note", + "this expression has type {type}", + {"type": types.TypePrinter().name(value.type)}, + value.loc) + 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, + target_desc) + + 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_mutable(node.target.type): + # If the target is mutable, op-assignment will allocate + # in the youngest region. + self.visit_assignment(node.target, node.value, is_aug_assign=True) + + def visit_Return(self, node): + region = self._region_of(node.value) + if 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 a mutable value that does not live forever", {}, + node.value.loc, notes=self._diagnostics_for(region, node.value.loc) + [note]) + self.engine.process(diag) + + def visit_Raise(self, node): + if builtins.is_mutable(node.exc.type): + note = diagnostic.Diagnostic("note", + "this expression has type {type}", + {"type": types.TypePrinter().name(node.exc.type)}, + node.exc.loc) + diag = diagnostic.Diagnostic("error", + "cannot raise a mutable value", {}, + node.exc.loc, notes=[note]) + self.engine.process(diag) From 4785f0a2de838b6b0b9178154a2c0e959cfd63d9 Mon Sep 17 00:00:00 2001 From: whitequark Date: Sat, 4 Jul 2015 04:27:15 +0300 Subject: [PATCH 075/369] Don't error out in inferencer if builtin arguments have polymorphic types. --- artiq/compiler/transforms/inferencer.py | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/artiq/compiler/transforms/inferencer.py b/artiq/compiler/transforms/inferencer.py index 6694b6129..37de32532 100644 --- a/artiq/compiler/transforms/inferencer.py +++ b/artiq/compiler/transforms/inferencer.py @@ -412,9 +412,13 @@ class Inferencer(algorithm.Visitor): 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): - pass + self._unify(node.type, builtins.TInt(), + node.loc, None) elif len(node.args) == 1 and len(node.keywords) == 1 and \ builtins.is_numeric(node.args[0].type) and \ node.keywords[0].arg == 'width': @@ -441,6 +445,9 @@ class Inferencer(algorithm.Visitor): 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 @@ -462,6 +469,8 @@ class Inferencer(algorithm.Visitor): if builtins.is_iterable(arg.type): pass + elif types.is_var(arg.type): + pass # undetermined yet else: note = diagnostic.Diagnostic("note", "this expression has type {type}", @@ -489,7 +498,11 @@ class Inferencer(algorithm.Visitor): self._unify(arg.type, range_tvar, arg.loc, None) - if not builtins.is_numeric(arg.type): + if builtins.is_numeric(arg.type): + pass + 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)}, @@ -514,6 +527,8 @@ class Inferencer(algorithm.Visitor): if builtins.is_iterable(arg.type): pass + elif types.is_var(arg.type): + pass # undetermined yet else: note = diagnostic.Diagnostic("note", "this expression has type {type}", From 549c110e7ceb0f499a82003fec6121b8cae79966 Mon Sep 17 00:00:00 2001 From: whitequark Date: Sat, 4 Jul 2015 04:27:24 +0300 Subject: [PATCH 076/369] Fix types.TFunction.fold. --- artiq/compiler/types.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/artiq/compiler/types.py b/artiq/compiler/types.py index 7e8c611f6..eb89809b1 100644 --- a/artiq/compiler/types.py +++ b/artiq/compiler/types.py @@ -194,15 +194,15 @@ class TFunction(Type): def fold(self, accum, fn): for arg in self.args: - accum = arg.fold(accum, fn) + 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): - return "py2llvm.types.TFunction(%s, %s, %s)" % \ - (repr(self.args), repr(self.optargs), repr(self.ret)) + return "py2llvm.types.TFunction({}, {}, {})".format( + repr(self.args), repr(self.optargs), repr(self.ret)) def __eq__(self, other): return isinstance(other, TFunction) and \ From 7c52910dc59d28c7429f88154b10fad05a4f4de2 Mon Sep 17 00:00:00 2001 From: whitequark Date: Sat, 11 Jul 2015 18:46:37 +0300 Subject: [PATCH 077/369] Add a basic SSA IR. --- artiq/compiler/ir.py | 319 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 319 insertions(+) create mode 100644 artiq/compiler/ir.py diff --git a/artiq/compiler/ir.py b/artiq/compiler/ir.py new file mode 100644 index 000000000..10c926ae7 --- /dev/null +++ b/artiq/compiler/ir.py @@ -0,0 +1,319 @@ +""" +The :mod:`ir` module contains the intermediate representation +of the ARTIQ compiler. +""" + +from . import types, builtins + +# Generic SSA IR classes + +def escape_name(name): + if all([isalnum(x) or x == "." for x in name]): + return name + else: + return "\"{}\"".format(name.replace("\"", "\\\"")) + +class TSSABasicBlock(types.TMono): + def __init__(self): + super().__init__("ssa.basic_block") + +class TSSAOption(types.TMono): + def __init__(self, inner): + super().__init__("ssa.option", {"inner": inner}) + +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=builtins.TNone()): + self.uses, self.type = set(), typ + + def replace_all_uses_with(self, value): + for user in self.uses: + user.replace_uses_of(self, value) + +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=builtins.TNone(), 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): + return "{} %{}".format(types.TypePrinter().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, typ=builtins.TNone(), name="", operands=[]): + super().__init__(typ, name) + self.operands = [] + self.set_operands(operands) + + def set_operands(self, new_operands): + for operand in self.operands: + operand.uses.remove(self) + self.operands = new_operands + for operand in self.operands: + operand.uses.add(self) + + def drop_references(self): + self.set_operands([]) + + def replace_uses_of(self, value, replacement): + assert value in operands + + for index, operand in enumerate(operands): + if operand == value: + operands[index] = replacement + + value.uses.remove(self) + replacement.uses.add(self) + +class Instruction(User): + """ + An SSA instruction. + """ + + def __init__(self, typ=builtins.TNone(), name="", operands=[]): + super().__init__(typ, name, operands) + self.basic_block = None + + 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() + + 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 __str__(self): + if builtins.is_none(self.type): + prefix = "" + else: + prefix = "%{} = {} ".format(escape_name(self.name), + types.TypePrinter().name(self.type)) + + if any(self.operands): + return "{} {} {}".format(prefix, self.opcode(), + ", ".join([operand.as_operand() for operand in self.operands])) + else: + return "{} {}".format(prefix, self.opcode()) + +class Phi(Instruction): + """ + An SSA instruction that joins data flow. + """ + + def opcode(self): + return "phi" + + def incoming(self): + operand_iter = iter(self.operands) + while True: + yield next(operand_iter), next(operand_iter) + + def incoming_blocks(self): + (block for (block, value) in self.incoming()) + + def incoming_values(self): + (value for (block, value) in self.incoming()) + + def incoming_value_for_block(self, target_block): + for (block, value) 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) + self.operands.append(block) + + def __str__(self): + if builtins.is_none(self.type): + prefix = "" + else: + prefix = "%{} = {} ".format(escape_name(self.name), + types.TypePrinter().name(self.type)) + + if any(self.operands): + operand_list = ["%{} => %{}".format(escape_name(block.name), escape_name(value.name)) + for operand in self.operands] + return "{} {} [{}]".format(prefix, self.opcode(), ", ".join(operand_list)) + +class Terminator(Instruction): + """ + An SSA instruction that performs control flow. + """ + + def successors(self): + [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 :) + """ + + def __init__(self, name="", instructions=[]): + super().__init__(TSSABasicBlock(), name) + self.instructions = [] + self.set_instructions(instructions) + + def remove_from_parent(self): + if self.function is not None: + self.function.remove(self) + + def prepend(self, insn): + insn.set_basic_block(self) + self.instructions.insert(0, insn) + + def append(self, insn): + insn.set_basic_block(self) + self.instructions.append(insn) + + def index(self, insn): + return self.instructions.index(insn) + + def insert(self, before, insn): + insn.set_basic_block(self) + self.instructions.insert(self.index(before), insn) + + def remove(self, insn): + insn._detach() + self.instructions.remove(insn) + + def replace(self, insn, replacement): + self.insert(insn, replacement) + self.remove(insn) + + def terminator(self): + assert isinstance(self.instructions[-1], Terminator) + return self.instructions[-1] + + def successors(self): + return self.terminator().successors() + + def predecessors(self): + assert self.function is not None + self.function.predecessors_of(self) + + def __str__(self): + lines = ["{}:".format(escape_name(self.name))] + for insn in self.instructions: + lines.append(str(insn)) + return "\n".join(lines) + +class Argument(NamedValue): + """ + A function argument. + """ + + def __str__(self): + return self.as_operand() + +class Function(Value): + """ + A function containing SSA IR. + """ + + def __init__(self, typ, name, arguments): + self.type, self.name, self.arguments = typ, name, [] + self.set_arguments(arguments) + self.basic_blocks = set() + self.names = set() + + def _remove_name(self, name): + self.names.remove(name) + + def _add_name(self, base_name): + name, counter = base_name, 1 + while name in self.names or name == "": + if base_name == "": + name = str(counter) + else: + name = "{}.{}".format(name, counter) + counter += 1 + + 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.add(basic_blocks) + + def remove(self, basic_block): + basic_block._detach() + self.basic_block.remove(basic_block) + + def predecessors_of(self, successor): + set(block for block in self.basic_blocks if successor in block.successors()) + + def as_operand(self): + return "{} @{}".format(types.TypePrinter().name(self.type), + escape_name(self.name)) + +# Python-specific SSA IR classes From 7c9afcce854c2d5c68a43d40e512b1181b112438 Mon Sep 17 00:00:00 2001 From: whitequark Date: Mon, 13 Jul 2015 20:52:48 +0300 Subject: [PATCH 078/369] Fix Python default argument fiasco. --- artiq/compiler/ir.py | 37 ++++++++++++++++++++++++++----------- 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/artiq/compiler/ir.py b/artiq/compiler/ir.py index 10c926ae7..d37de2ea0 100644 --- a/artiq/compiler/ir.py +++ b/artiq/compiler/ir.py @@ -29,7 +29,7 @@ class Value: :ivar uses: (list of :class:`Value`) values that use this value """ - def __init__(self, typ=builtins.TNone()): + def __init__(self, typ): self.uses, self.type = set(), typ def replace_all_uses_with(self, value): @@ -44,7 +44,7 @@ class NamedValue(Value): :ivar function: (:class:`Function`) function containing this value """ - def __init__(self, typ=builtins.TNone(), name=""): + def __init__(self, typ, name): super().__init__(typ) self.name, self.function = name, None @@ -77,10 +77,11 @@ class User(NamedValue): :ivar operands: (list of :class:`Value`) operands of this value """ - def __init__(self, typ=builtins.TNone(), name="", operands=[]): + def __init__(self, operands, typ, name): super().__init__(typ, name) self.operands = [] - self.set_operands(operands) + if operands is not None: + self.set_operands(operands) def set_operands(self, new_operands): for operand in self.operands: @@ -107,8 +108,8 @@ class Instruction(User): An SSA instruction. """ - def __init__(self, typ=builtins.TNone(), name="", operands=[]): - super().__init__(typ, name, operands) + def __init__(self, operands, typ, name=""): + super().__init__(operands, typ, name) self.basic_block = None def set_basic_block(self, new_basic_block): @@ -157,8 +158,14 @@ class Instruction(User): 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" @@ -202,20 +209,27 @@ class Terminator(Instruction): """ def successors(self): - [operand for operand in self.operands if isinstance(operand, BasicBlock)] + 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 :) + :ivar instructions: (list of :class:`Instruction`) """ - def __init__(self, name="", instructions=[]): + def __init__(self, instructions, name=""): super().__init__(TSSABasicBlock(), 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) @@ -274,10 +288,11 @@ class Function(Value): """ def __init__(self, typ, name, arguments): - self.type, self.name, self.arguments = typ, name, [] - self.set_arguments(arguments) + self.type, self.name = typ, name + self.arguments = [] self.basic_blocks = set() self.names = set() + self.set_arguments(arguments) def _remove_name(self, name): self.names.remove(name) From dbdd45acc5b3cd575630b5d888d6bc059ae24baa Mon Sep 17 00:00:00 2001 From: whitequark Date: Mon, 13 Jul 2015 20:52:55 +0300 Subject: [PATCH 079/369] Add missing return. --- artiq/compiler/ir.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/artiq/compiler/ir.py b/artiq/compiler/ir.py index d37de2ea0..9b37d9628 100644 --- a/artiq/compiler/ir.py +++ b/artiq/compiler/ir.py @@ -325,7 +325,7 @@ class Function(Value): self.basic_block.remove(basic_block) def predecessors_of(self, successor): - set(block for block in self.basic_blocks if successor in block.successors()) + return set(block for block in self.basic_blocks if successor in block.successors()) def as_operand(self): return "{} @{}".format(types.TypePrinter().name(self.type), From ebe243f8d9837141915ad193c57d6fb531bc8294 Mon Sep 17 00:00:00 2001 From: whitequark Date: Mon, 13 Jul 2015 20:53:02 +0300 Subject: [PATCH 080/369] Add printing of SSA functions. --- artiq/compiler/ir.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/artiq/compiler/ir.py b/artiq/compiler/ir.py index 9b37d9628..31405cd15 100644 --- a/artiq/compiler/ir.py +++ b/artiq/compiler/ir.py @@ -331,4 +331,16 @@ class Function(Value): return "{} @{}".format(types.TypePrinter().name(self.type), escape_name(self.name)) + def __str__(self): + printer = types.TypePrinter() + lines = [] + lines.append("{} {}({}) {{ ; type: {}".format( + printer.name(self.type.ret), self.name, + ", ".join([arg.as_operand() for arg in self.arguments]), + printer.name(self.type))) + for block in self.basic_blocks: + lines.append(str(block)) + lines.append("}") + return "\n".join(lines) + # Python-specific SSA IR classes From f417ef31a4971f460008e6ad2e4ab7a6322284ef Mon Sep 17 00:00:00 2001 From: whitequark Date: Tue, 14 Jul 2015 06:42:09 +0300 Subject: [PATCH 081/369] Make binop coercion look through CoerceT nodes. This fixes inference for "x = 1 + 1" after int monomorphization. --- artiq/compiler/asttyped.py | 1 - artiq/compiler/transforms/inferencer.py | 7 ++++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/artiq/compiler/asttyped.py b/artiq/compiler/asttyped.py index 5184c7401..f29b59d4a 100644 --- a/artiq/compiler/asttyped.py +++ b/artiq/compiler/asttyped.py @@ -4,7 +4,6 @@ typing information. """ from pythonparser import ast -from pythonparser.algorithm import Visitor as ASTVisitor class commontyped(ast.commonloc): """A mixin for typed AST nodes.""" diff --git a/artiq/compiler/transforms/inferencer.py b/artiq/compiler/transforms/inferencer.py index 37de32532..c06453601 100644 --- a/artiq/compiler/transforms/inferencer.py +++ b/artiq/compiler/transforms/inferencer.py @@ -183,7 +183,12 @@ class Inferencer(algorithm.Visitor): def _coerce_numeric(self, nodes, map_return=lambda typ: typ): # See https://docs.python.org/3/library/stdtypes.html#numeric-types-int-float-complex. - node_types = [node.type for node in nodes] + node_types = [] + for node in nodes: + if isinstance(node, asttyped.CoerceT): + node_types.append(node.expr.type) + else: + node_types.append(node.type) if any(map(types.is_var, node_types)): # not enough info yet return elif not all(map(builtins.is_numeric, node_types)): From bdcb24108b660bc2ddeabb71a0435978c0569147 Mon Sep 17 00:00:00 2001 From: whitequark Date: Tue, 14 Jul 2015 06:44:16 +0300 Subject: [PATCH 082/369] Add basic IR generator. --- artiq/compiler/ir.py | 104 +++++++++++++-- artiq/compiler/module.py | 7 +- artiq/compiler/testbench/irgen.py | 19 +++ artiq/compiler/transforms/__init__.py | 1 + artiq/compiler/transforms/ir_generator.py | 149 ++++++++++++++++++++++ lit-test/compiler/irgen/empty.py | 7 + lit-test/compiler/irgen/eval.py | 9 ++ lit-test/compiler/irgen/if.py | 21 +++ lit-test/compiler/irgen/while.py | 25 ++++ 9 files changed, 332 insertions(+), 10 deletions(-) create mode 100644 artiq/compiler/testbench/irgen.py create mode 100644 artiq/compiler/transforms/ir_generator.py create mode 100644 lit-test/compiler/irgen/empty.py create mode 100644 lit-test/compiler/irgen/eval.py create mode 100644 lit-test/compiler/irgen/if.py create mode 100644 lit-test/compiler/irgen/while.py diff --git a/artiq/compiler/ir.py b/artiq/compiler/ir.py index 31405cd15..7b1c0432c 100644 --- a/artiq/compiler/ir.py +++ b/artiq/compiler/ir.py @@ -8,7 +8,7 @@ from . import types, builtins # Generic SSA IR classes def escape_name(name): - if all([isalnum(x) or x == "." for x in name]): + if all([str.isalnum(x) or x == "." for x in name]): return name else: return "\"{}\"".format(name.replace("\"", "\\\"")) @@ -36,6 +36,24 @@ class Value: for user in self.uses: user.replace_uses_of(self, value) +class Constant(Value): + """ + A constant value. + + :ivar value: (None, True or False) value + """ + + def __init__(self, value, typ): + super().__init__(typ) + self.value = value + + def as_operand(self): + return str(self) + + def __str__(self): + return "{} {}".format(types.TypePrinter().name(self.type), + repr(self.value)) + class NamedValue(Value): """ An SSA value that has a name. @@ -150,10 +168,10 @@ class Instruction(User): types.TypePrinter().name(self.type)) if any(self.operands): - return "{} {} {}".format(prefix, self.opcode(), + return "{}{} {}".format(prefix, self.opcode(), ", ".join([operand.as_operand() for operand in self.operands])) else: - return "{} {}".format(prefix, self.opcode()) + return "{}{}".format(prefix, self.opcode()) class Phi(Instruction): """ @@ -201,7 +219,7 @@ class Phi(Instruction): if any(self.operands): operand_list = ["%{} => %{}".format(escape_name(block.name), escape_name(value.name)) for operand in self.operands] - return "{} {} [{}]".format(prefix, self.opcode(), ", ".join(operand_list)) + return "{}{} [{}]".format(prefix, self.opcode(), ", ".join(operand_list)) class Terminator(Instruction): """ @@ -241,6 +259,7 @@ class BasicBlock(NamedValue): def append(self, insn): insn.set_basic_block(self) self.instructions.append(insn) + return insn def index(self, insn): return self.instructions.index(insn) @@ -248,17 +267,22 @@ class BasicBlock(NamedValue): def insert(self, before, insn): insn.set_basic_block(self) self.instructions.insert(self.index(before), insn) + return insn def remove(self, insn): insn._detach() self.instructions.remove(insn) + return insn def replace(self, insn, replacement): self.insert(insn, replacement) self.remove(insn) + def is_terminated(self): + return any(self.instructions) and isinstance(self.instructions[-1], Terminator) + def terminator(self): - assert isinstance(self.instructions[-1], Terminator) + assert self.is_terminated() return self.instructions[-1] def successors(self): @@ -271,7 +295,7 @@ class BasicBlock(NamedValue): def __str__(self): lines = ["{}:".format(escape_name(self.name))] for insn in self.instructions: - lines.append(str(insn)) + lines.append(" " + str(insn)) return "\n".join(lines) class Argument(NamedValue): @@ -290,7 +314,7 @@ class Function(Value): def __init__(self, typ, name, arguments): self.type, self.name = typ, name self.arguments = [] - self.basic_blocks = set() + self.basic_blocks = [] self.names = set() self.set_arguments(arguments) @@ -318,14 +342,14 @@ class Function(Value): def add(self, basic_block): basic_block._set_function(self) - self.basic_blocks.add(basic_blocks) + self.basic_blocks.append(basic_block) def remove(self, basic_block): basic_block._detach() self.basic_block.remove(basic_block) def predecessors_of(self, successor): - return set(block for block in self.basic_blocks if successor in block.successors()) + return [block for block in self.basic_blocks if successor in block.successors()] def as_operand(self): return "{} @{}".format(types.TypePrinter().name(self.type), @@ -344,3 +368,65 @@ class Function(Value): return "\n".join(lines) # Python-specific SSA IR classes + +class Branch(Terminator): + """ + An unconditional branch instruction. + """ + + """ + :param target: (:class:`BasicBlock`) branch target + """ + def __init__(self, target, name=""): + super().__init__([target], builtins.TNone(), name) + + def opcode(self): + return "branch" + +class BranchIf(Terminator): + """ + A conditional branch instruction. + """ + + """ + :param cond: (:class:`Value`) branch condition + :param if_true: (:class:`BasicBlock`) branch target if expression is truthful + :param if_false: (:class:`BasicBlock`) branch target if expression is falseful + """ + def __init__(self, cond, if_true, if_false, name=""): + super().__init__([cond, if_true, if_false], builtins.TNone(), name) + + def opcode(self): + return "branch_if" + +class Return(Terminator): + """ + A return instruction. + """ + + """ + :param value: (:class:`Value`) return value + """ + def __init__(self, value, name=""): + super().__init__([value], builtins.TNone(), name) + + def opcode(self): + return "return" + +class Eval(Instruction): + """ + An instruction that evaluates an AST fragment. + """ + + """ + :param ast: (:class:`.asttyped.AST`) return value + """ + def __init__(self, ast, name=""): + super().__init__([], ast.type, name) + self.ast = ast + + def opcode(self): + return "eval" + + def __str__(self): + return super().__str__() + " `{}`".format(self.ast.loc.source()) diff --git a/artiq/compiler/module.py b/artiq/compiler/module.py index 2b847eb27..833896d79 100644 --- a/artiq/compiler/module.py +++ b/artiq/compiler/module.py @@ -11,11 +11,14 @@ class Module: if engine is None: engine = diagnostic.Engine(all_errors_are_fatal=True) + module_name, _ = os.path.splitext(os.path.basename(source_buffer.name)) + asttyped_rewriter = transforms.ASTTypedRewriter(engine=engine) inferencer = transforms.Inferencer(engine=engine) int_monomorphizer = transforms.IntMonomorphizer(engine=engine) monomorphism_validator = validators.MonomorphismValidator(engine=engine) escape_validator = validators.EscapeValidator(engine=engine) + ir_generator = transforms.IRGenerator(engine=engine, module_name=module_name) parsetree, comments = parse_buffer(source_buffer, engine=engine) typedtree = asttyped_rewriter.visit(parsetree) @@ -24,9 +27,11 @@ class Module: inferencer.visit(typedtree) monomorphism_validator.visit(typedtree) escape_validator.visit(typedtree) + ir_generator.visit(typedtree) - self.name = os.path.basename(source_buffer.name) + self.name = module_name self.globals = asttyped_rewriter.globals + self.ir = ir_generator.functions @classmethod def from_string(cls, source_string, name="input.py", first_line=1, engine=None): diff --git a/artiq/compiler/testbench/irgen.py b/artiq/compiler/testbench/irgen.py new file mode 100644 index 000000000..f2701a301 --- /dev/null +++ b/artiq/compiler/testbench/irgen.py @@ -0,0 +1,19 @@ +import sys, fileinput +from pythonparser import diagnostic +from .. import Module + +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.from_string("".join(fileinput.input()).expandtabs(), engine=engine) + for fn in mod.ir: + print(fn) + +if __name__ == "__main__": + main() diff --git a/artiq/compiler/transforms/__init__.py b/artiq/compiler/transforms/__init__.py index 795c14c79..4f059a686 100644 --- a/artiq/compiler/transforms/__init__.py +++ b/artiq/compiler/transforms/__init__.py @@ -1,3 +1,4 @@ from .asttyped_rewriter import ASTTypedRewriter from .inferencer import Inferencer from .int_monomorphizer import IntMonomorphizer +from .ir_generator import IRGenerator diff --git a/artiq/compiler/transforms/ir_generator.py b/artiq/compiler/transforms/ir_generator.py new file mode 100644 index 000000000..a26d413cf --- /dev/null +++ b/artiq/compiler/transforms/ir_generator.py @@ -0,0 +1,149 @@ +""" +:class:`IRGenerator` transforms typed AST into ARTIQ intermediate +representation. +""" + +from collections import OrderedDict +from pythonparser import algorithm, diagnostic, ast +from .. import types, builtins, ir + +# 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 IRGenerator(algorithm.Visitor): + def __init__(self, module_name, engine): + self.engine = engine + self.functions = [] + self.name = [module_name] + self.current_function = None + self.current_block = None + self.break_target, self.continue_target = None, None + + def add_block(self): + block = ir.BasicBlock([]) + self.current_function.add(block) + return block + + def append(self, insn): + return self.current_block.append(insn) + + def terminate(self, insn): + if not self.current_block.is_terminated(): + self.append(insn) + + def visit(self, obj): + if isinstance(obj, list): + for elt in obj: + self.visit(elt) + if self.current_block.is_terminated(): + break + elif isinstance(obj, ast.AST): + return self._visit_one(obj) + + def visit_function(self, name, typ, inner): + try: + old_name, self.name = self.name, self.name + [name] + + args = [] + for arg_name in typ.args: + args.append(ir.Argument(typ.args[arg_name], arg_name)) + for arg_name in typ.optargs: + args.append(ir.Argument(ir.TSSAOption(typ.optargs[arg_name]), arg_name)) + + func = ir.Function(typ, ".".join(self.name), args) + self.functions.append(func) + old_func, self.current_function = self.current_function, func + + self.current_block = self.add_block() + inner() + finally: + self.name = old_name + self.current_function = old_func + + def visit_ModuleT(self, node): + def inner(): + self.generic_visit(node) + + return_value = ir.Constant(None, builtins.TNone()) + self.terminate(ir.Return(return_value)) + + typ = types.TFunction(OrderedDict(), OrderedDict(), builtins.TNone()) + self.visit_function('__modinit__', typ, inner) + + def visit_FunctionDefT(self, node): + self.visit_function(node.name, node.signature_type.find(), + lambda: self.generic_visit(node)) + + def visit_Return(self, node): + if node.value is None: + return_value = ir.Constant(None, builtins.TNone()) + self.append(ir.Return(return_value)) + else: + expr = self.append(ir.Eval(node.value)) + self.append(ir.Return(expr)) + + def visit_Expr(self, node): + self.append(ir.Eval(node.value)) + + # Assign + # AugAssign + + def visit_If(self, node): + cond = self.append(ir.Eval(node.test)) + head = self.current_block + + if_true = self.add_block() + self.current_block = if_true + self.visit(node.body) + + if_false = self.add_block() + self.current_block = if_false + self.visit(node.orelse) + + tail = self.add_block() + self.current_block = tail + if not if_true.is_terminated(): + if_true.append(ir.Branch(tail)) + if not if_false.is_terminated(): + if_false.append(ir.Branch(tail)) + head.append(ir.BranchIf(cond, if_true, if_false)) + + def visit_While(self, node): + try: + head = self.add_block() + self.append(ir.Branch(head)) + self.current_block = head + + tail_tramp = self.add_block() + old_break, self.break_target = self.break_target, tail_tramp + + body = self.add_block() + old_continue, self.continue_target = self.continue_target, body + self.current_block = body + self.visit(node.body) + + tail = self.add_block() + self.current_block = tail + self.visit(node.orelse) + + cond = head.append(ir.Eval(node.test)) + head.append(ir.BranchIf(cond, body, tail)) + if not body.is_terminated(): + body.append(ir.Branch(tail)) + tail_tramp.append(ir.Branch(tail)) + finally: + self.break_target = old_break + self.continue_target = old_continue + + # For + + def visit_Break(self, node): + self.append(ir.Branch(self.break_target)) + + def visit_Continue(self, node): + self.append(ir.Branch(self.continue_target)) + + # Raise + # Try + + # With diff --git a/lit-test/compiler/irgen/empty.py b/lit-test/compiler/irgen/empty.py new file mode 100644 index 000000000..85beee74d --- /dev/null +++ b/lit-test/compiler/irgen/empty.py @@ -0,0 +1,7 @@ +# RUN: %python -m artiq.compiler.testbench.irgen %s >%t +# RUN: OutputCheck %s --file-to-check=%t + +# CHECK-L: NoneType input.__modinit__() { +# CHECK-L: 1: +# CHECK-L: return NoneType None +# CHECK-L: } diff --git a/lit-test/compiler/irgen/eval.py b/lit-test/compiler/irgen/eval.py new file mode 100644 index 000000000..cd88b9b1e --- /dev/null +++ b/lit-test/compiler/irgen/eval.py @@ -0,0 +1,9 @@ +# RUN: %python -m artiq.compiler.testbench.irgen %s >%t +# RUN: OutputCheck %s --file-to-check=%t + +2 + 2 +# CHECK-L: NoneType input.__modinit__() { +# CHECK-L: 1: +# CHECK-L: %2 = int(width=32) eval `2 + 2` +# CHECK-L: return NoneType None +# CHECK-L: } diff --git a/lit-test/compiler/irgen/if.py b/lit-test/compiler/irgen/if.py new file mode 100644 index 000000000..28d25d944 --- /dev/null +++ b/lit-test/compiler/irgen/if.py @@ -0,0 +1,21 @@ +# RUN: %python -m artiq.compiler.testbench.irgen %s >%t +# RUN: OutputCheck %s --file-to-check=%t + +if 1: + 2 +else: + 3 + +# CHECK-L: NoneType input.__modinit__() { +# CHECK-L: 1: +# CHECK-L: %2 = int(width=32) eval `1` +# CHECK-L: branch_if int(width=32) %2, ssa.basic_block %3, ssa.basic_block %5 +# CHECK-L: 3: +# CHECK-L: %4 = int(width=32) eval `2` +# CHECK-L: branch ssa.basic_block %7 +# CHECK-L: 5: +# CHECK-L: %6 = int(width=32) eval `3` +# CHECK-L: branch ssa.basic_block %7 +# CHECK-L: 7: +# CHECK-L: return NoneType None +# CHECK-L: } diff --git a/lit-test/compiler/irgen/while.py b/lit-test/compiler/irgen/while.py new file mode 100644 index 000000000..6ec344be0 --- /dev/null +++ b/lit-test/compiler/irgen/while.py @@ -0,0 +1,25 @@ +# RUN: %python -m artiq.compiler.testbench.irgen %s >%t +# RUN: OutputCheck %s --file-to-check=%t + +while 1: + 2 +else: + 3 +4 + +# CHECK-L: NoneType input.__modinit__() { +# CHECK-L: 1: +# CHECK-L: branch ssa.basic_block %2 +# CHECK-L: 2: +# CHECK-L: %9 = int(width=32) eval `1` +# CHECK-L: branch_if int(width=32) %9, ssa.basic_block %5, ssa.basic_block %7 +# CHECK-L: 4: +# CHECK-L: branch ssa.basic_block %7 +# CHECK-L: 5: +# CHECK-L: %6 = int(width=32) eval `2` +# CHECK-L: branch ssa.basic_block %7 +# CHECK-L: 7: +# CHECK-L: %8 = int(width=32) eval `3` +# CHECK-L: %13 = int(width=32) eval `4` +# CHECK-L: return NoneType None +# CHECK-L: } From 9ff9f85f1922da4f9d02c113acdd9d826feeced9 Mon Sep 17 00:00:00 2001 From: whitequark Date: Tue, 14 Jul 2015 22:18:38 +0300 Subject: [PATCH 083/369] Add accessors to instructions. --- artiq/compiler/ir.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/artiq/compiler/ir.py b/artiq/compiler/ir.py index 7b1c0432c..5f6688759 100644 --- a/artiq/compiler/ir.py +++ b/artiq/compiler/ir.py @@ -383,6 +383,9 @@ class Branch(Terminator): def opcode(self): return "branch" + def target(self): + return self.operands[0] + class BranchIf(Terminator): """ A conditional branch instruction. @@ -399,6 +402,15 @@ class BranchIf(Terminator): def opcode(self): return "branch_if" + def condition(self): + return self.operands[0] + + def if_true(self): + return self.operands[1] + + def if_false(self): + return self.operands[2] + class Return(Terminator): """ A return instruction. @@ -413,6 +425,9 @@ class Return(Terminator): def opcode(self): return "return" + def value(self): + return self.operands[0] + class Eval(Instruction): """ An instruction that evaluates an AST fragment. From c724e024cea9edc7d5c7d05c60223206e5f4f2dc Mon Sep 17 00:00:00 2001 From: whitequark Date: Wed, 15 Jul 2015 06:33:44 +0300 Subject: [PATCH 084/369] Fix inference for multiple-target assignments. --- artiq/compiler/transforms/inferencer.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/artiq/compiler/transforms/inferencer.py b/artiq/compiler/transforms/inferencer.py index c06453601..869b87340 100644 --- a/artiq/compiler/transforms/inferencer.py +++ b/artiq/compiler/transforms/inferencer.py @@ -652,12 +652,9 @@ class Inferencer(algorithm.Visitor): def visit_Assign(self, node): self.generic_visit(node) - if len(node.targets) > 1: - self._unify(types.TTuple([x.type for x in node.targets]), node.value.type, - node.targets[0].loc.join(node.targets[-1].loc), node.value.loc) - else: - self._unify(node.targets[0].type, node.value.type, - node.targets[0].loc, node.value.loc) + 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) From 227f97f8a3272788073b567a626730a6fe7fb06f Mon Sep 17 00:00:00 2001 From: whitequark Date: Thu, 16 Jul 2015 04:21:21 +0300 Subject: [PATCH 085/369] Add inference for Index, Slice and ExtSlice. --- artiq/compiler/transforms/inferencer.py | 31 ++++++++++++++++++- .../compiler/inferencer/error_subscript.py | 10 ++++++ 2 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 lit-test/compiler/inferencer/error_subscript.py diff --git a/artiq/compiler/transforms/inferencer.py b/artiq/compiler/transforms/inferencer.py index 869b87340..7fbf09813 100644 --- a/artiq/compiler/transforms/inferencer.py +++ b/artiq/compiler/transforms/inferencer.py @@ -108,9 +108,38 @@ class Inferencer(algorithm.Visitor): collection.loc, []) self.engine.process(diag) + def visit_Index(self, node): + value = node.value + if types.is_tuple(value.type): + diag = diagnostic.Diagnostic("error", + "multi-dimensional slices are not supported", {}, + node.loc, []) + self.engine.process(diag) + else: + self._unify(value.type, builtins.TInt(), + value.loc, None) + + def visit_Slice(self, node): + for operand in (node.lower, node.upper, node.step): + if operand is not None: + self._unify(operand.type, builtins.TInt(), + 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) - self._unify_iterable(element=node, collection=node.value) + if isinstance(node.slice, ast.Index): + self._unify_iterable(element=node, collection=node.value) + elif isinstance(node.slice, ast.Slice): + 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) diff --git a/lit-test/compiler/inferencer/error_subscript.py b/lit-test/compiler/inferencer/error_subscript.py new file mode 100644 index 000000000..0aadb3289 --- /dev/null +++ b/lit-test/compiler/inferencer/error_subscript.py @@ -0,0 +1,10 @@ +# 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 slices are not supported +x[1,2] + +# CHECK-L: ${LINE:+1}: error: multi-dimensional slices are not supported +x[1:2,3:4] From 53fb03d1bf7324c38931bb9ffa7dc4dff5ec17d2 Mon Sep 17 00:00:00 2001 From: whitequark Date: Thu, 16 Jul 2015 14:52:41 +0300 Subject: [PATCH 086/369] Restrict comprehensions to single for and no if clauses. --- .../compiler/transforms/asttyped_rewriter.py | 23 +++++++++++++++---- artiq/compiler/transforms/inferencer.py | 12 ++++++++++ .../inferencer/error_comprehension.py | 8 +++++++ 3 files changed, 39 insertions(+), 4 deletions(-) create mode 100644 lit-test/compiler/inferencer/error_comprehension.py diff --git a/artiq/compiler/transforms/asttyped_rewriter.py b/artiq/compiler/transforms/asttyped_rewriter.py index de72fce3e..e89e7ac9c 100644 --- a/artiq/compiler/transforms/asttyped_rewriter.py +++ b/artiq/compiler/transforms/asttyped_rewriter.py @@ -57,6 +57,25 @@ class LocalExtractor(algorithm.Visitor): for if_ in node.ifs: 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 @@ -66,10 +85,6 @@ class LocalExtractor(algorithm.Visitor): visit_Module = visit_root # don't look at inner scopes visit_ClassDef = visit_root visit_Lambda = visit_root - visit_DictComp = visit_root - visit_ListComp = visit_root - visit_SetComp = visit_root - visit_GeneratorExp = visit_root def visit_FunctionDef(self, node): if self.in_root: diff --git a/artiq/compiler/transforms/inferencer.py b/artiq/compiler/transforms/inferencer.py index 7fbf09813..dbb9aeb04 100644 --- a/artiq/compiler/transforms/inferencer.py +++ b/artiq/compiler/transforms/inferencer.py @@ -390,11 +390,23 @@ class Inferencer(algorithm.Visitor): 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) diff --git a/lit-test/compiler/inferencer/error_comprehension.py b/lit-test/compiler/inferencer/error_comprehension.py new file mode 100644 index 000000000..d586dd657 --- /dev/null +++ b/lit-test/compiler/inferencer/error_comprehension.py @@ -0,0 +1,8 @@ +# 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 []] From e9416f4707ced2dcff77b2d838989c1b1933e77c Mon Sep 17 00:00:00 2001 From: whitequark Date: Thu, 16 Jul 2015 14:53:24 +0300 Subject: [PATCH 087/369] Convert Slice into typed SliceT. --- artiq/compiler/asttyped.py | 3 +++ artiq/compiler/transforms/asttyped_rewriter.py | 9 +++++++++ artiq/compiler/transforms/inferencer.py | 6 ++++-- 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/artiq/compiler/asttyped.py b/artiq/compiler/asttyped.py index f29b59d4a..8e4674c99 100644 --- a/artiq/compiler/asttyped.py +++ b/artiq/compiler/asttyped.py @@ -36,6 +36,9 @@ class ExceptHandlerT(ast.ExceptHandler): _fields = ("filter", "name", "body") # rename ast.ExceptHandler.type _types = ("name_type",) +class SliceT(ast.Slice, commontyped): + pass + class AttributeT(ast.Attribute, commontyped): pass class BinOpT(ast.BinOp, commontyped): diff --git a/artiq/compiler/transforms/asttyped_rewriter.py b/artiq/compiler/transforms/asttyped_rewriter.py index e89e7ac9c..094bce534 100644 --- a/artiq/compiler/transforms/asttyped_rewriter.py +++ b/artiq/compiler/transforms/asttyped_rewriter.py @@ -276,6 +276,15 @@ class ASTTypedRewriter(algorithm.Transformer): 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(), diff --git a/artiq/compiler/transforms/inferencer.py b/artiq/compiler/transforms/inferencer.py index dbb9aeb04..45d2cf40e 100644 --- a/artiq/compiler/transforms/inferencer.py +++ b/artiq/compiler/transforms/inferencer.py @@ -119,10 +119,12 @@ class Inferencer(algorithm.Visitor): self._unify(value.type, builtins.TInt(), value.loc, None) - def visit_Slice(self, node): + def visit_SliceT(self, node): + 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, builtins.TInt(), + self._unify(operand.type, node.type, operand.loc, None) def visit_ExtSlice(self, node): From 5000f87dfc67850bc289120bffb749129be68d21 Mon Sep 17 00:00:00 2001 From: whitequark Date: Thu, 16 Jul 2015 14:55:23 +0300 Subject: [PATCH 088/369] Rename the field of CoerceT from expr to value. --- artiq/compiler/asttyped.py | 2 +- artiq/compiler/transforms/inferencer.py | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/artiq/compiler/asttyped.py b/artiq/compiler/asttyped.py index 8e4674c99..7d38bd823 100644 --- a/artiq/compiler/asttyped.py +++ b/artiq/compiler/asttyped.py @@ -92,4 +92,4 @@ class YieldFromT(ast.YieldFrom, commontyped): # Novel typed nodes class CoerceT(ast.expr, commontyped): - _fields = ('expr',) # other_expr deliberately not in _fields + _fields = ('value',) # other_value deliberately not in _fields diff --git a/artiq/compiler/transforms/inferencer.py b/artiq/compiler/transforms/inferencer.py index 45d2cf40e..cc224bb07 100644 --- a/artiq/compiler/transforms/inferencer.py +++ b/artiq/compiler/transforms/inferencer.py @@ -186,17 +186,17 @@ class Inferencer(algorithm.Visitor): def visit_CoerceT(self, node): self.generic_visit(node) - if builtins.is_numeric(node.type) and builtins.is_numeric(node.expr.type): + if builtins.is_numeric(node.type) and builtins.is_numeric(node.value.type): pass else: printer = types.TypePrinter() note = diagnostic.Diagnostic("note", "expression that required coercion to {typeb}", {"typeb": printer.name(node.type)}, - node.other_expr.loc) + node.other_value.loc) diag = diagnostic.Diagnostic("error", "cannot coerce {typea} to {typeb}", - {"typea": printer.name(node.expr.type), "typeb": printer.name(node.type)}, + {"typea": printer.name(node.value.type), "typeb": printer.name(node.type)}, node.loc, notes=[note]) self.engine.process(diag) @@ -205,9 +205,9 @@ class Inferencer(algorithm.Visitor): return coerced_node elif isinstance(coerced_node, asttyped.CoerceT): node = coerced_node - node.type, node.other_expr = typ, other_node + node.type, node.other_value = typ, other_node else: - node = asttyped.CoerceT(type=typ, expr=coerced_node, other_expr=other_node, + node = asttyped.CoerceT(type=typ, value=coerced_node, other_value=other_node, loc=coerced_node.loc) self.visit(node) return node @@ -217,7 +217,7 @@ class Inferencer(algorithm.Visitor): node_types = [] for node in nodes: if isinstance(node, asttyped.CoerceT): - node_types.append(node.expr.type) + node_types.append(node.value.type) else: node_types.append(node.type) if any(map(types.is_var, node_types)): # not enough info yet From a6950bf11dd534487bda577b549206180e11502b Mon Sep 17 00:00:00 2001 From: whitequark Date: Thu, 16 Jul 2015 14:56:39 +0300 Subject: [PATCH 089/369] Move builtin.is_{builtin,exn_constructor} to types. --- artiq/compiler/builtins.py | 13 ------------- artiq/compiler/transforms/inferencer.py | 18 +++++++++--------- artiq/compiler/types.py | 17 +++++++++++++++-- 3 files changed, 24 insertions(+), 24 deletions(-) diff --git a/artiq/compiler/builtins.py b/artiq/compiler/builtins.py index bbdc6462b..5fdd49708 100644 --- a/artiq/compiler/builtins.py +++ b/artiq/compiler/builtins.py @@ -120,19 +120,6 @@ def is_collection(typ): return isinstance(typ, types.TTuple) or \ types.is_mono(typ, "list") -def is_builtin(typ, name): - typ = typ.find() - return isinstance(typ, types.TBuiltin) and \ - typ.name == name - -def is_exn_constructor(typ, name=None): - typ = typ.find() - if name is not None: - return isinstance(typ, types.TExceptionConstructor) and \ - typ.name == name - else: - return isinstance(typ, types.TExceptionConstructor) - def is_mutable(typ): return typ.fold(False, lambda accum, typ: is_list(typ) or types.is_function(typ)) diff --git a/artiq/compiler/transforms/inferencer.py b/artiq/compiler/transforms/inferencer.py index cc224bb07..5400da937 100644 --- a/artiq/compiler/transforms/inferencer.py +++ b/artiq/compiler/transforms/inferencer.py @@ -432,7 +432,7 @@ class Inferencer(algorithm.Visitor): node.func.loc, notes=valid_forms) self.engine.process(diag) - if builtins.is_builtin(typ, "bool"): + if types.is_builtin(typ, "bool"): valid_forms = lambda: [ valid_form("bool() -> bool"), valid_form("bool(x:'a) -> bool") @@ -448,7 +448,7 @@ class Inferencer(algorithm.Visitor): self._unify(node.type, builtins.TBool(), node.loc, None) - elif builtins.is_builtin(typ, "int"): + elif types.is_builtin(typ, "int"): valid_forms = lambda: [ valid_form("int() -> int(width='a)"), valid_form("int(x:'a) -> int(width='b) where 'a is numeric"), @@ -482,7 +482,7 @@ class Inferencer(algorithm.Visitor): node.loc, None) else: diagnose(valid_forms()) - elif builtins.is_builtin(typ, "float"): + elif types.is_builtin(typ, "float"): valid_forms = lambda: [ valid_form("float() -> float"), valid_form("float(x:'a) -> float where 'a is numeric") @@ -501,7 +501,7 @@ class Inferencer(algorithm.Visitor): pass else: diagnose(valid_forms()) - elif builtins.is_builtin(typ, "list"): + 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") @@ -530,7 +530,7 @@ class Inferencer(algorithm.Visitor): self.engine.process(diag) else: diagnose(valid_forms()) - elif builtins.is_builtin(typ, "range"): + elif types.is_builtin(typ, "range"): valid_forms = lambda: [ valid_form("range(max:'a) -> range(elt='a)"), valid_form("range(min:'a, max:'a) -> range(elt='a)"), @@ -561,7 +561,7 @@ class Inferencer(algorithm.Visitor): self.engine.process(diag) else: diagnose(valid_forms()) - elif builtins.is_builtin(typ, "len"): + elif types.is_builtin(typ, "len"): valid_forms = lambda: [ valid_form("len(x:'a) -> int(width='b) where 'a is iterable"), ] @@ -588,7 +588,7 @@ class Inferencer(algorithm.Visitor): self.engine.process(diag) else: diagnose(valid_forms()) - elif builtins.is_builtin(typ, "round"): + elif types.is_builtin(typ, "round"): valid_forms = lambda: [ valid_form("round(x:float) -> int(width='a)"), ] @@ -604,7 +604,7 @@ class Inferencer(algorithm.Visitor): else: diagnose(valid_forms()) # TODO: add when it is clear what interface syscall() has - # elif builtins.is_builtin(typ, "syscall"): + # elif types.is_builtin(typ, "syscall"): # valid_Forms = lambda: [ # ] @@ -778,7 +778,7 @@ class Inferencer(algorithm.Visitor): def visit_ExceptHandlerT(self, node): self.generic_visit(node) - if not builtins.is_exn_constructor(node.filter.type): + 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)}, diff --git a/artiq/compiler/types.py b/artiq/compiler/types.py index eb89809b1..4cff68161 100644 --- a/artiq/compiler/types.py +++ b/artiq/compiler/types.py @@ -322,8 +322,21 @@ def is_tuple(typ, elts=None): def is_function(typ): return isinstance(typ.find(), TFunction) -def is_builtin(typ): - return isinstance(typ.find(), TBuiltin) +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_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 get_value(typ): typ = typ.find() From b58fa9067d5d4bb6e1882dd1af97d4bc5d65ce9e Mon Sep 17 00:00:00 2001 From: whitequark Date: Thu, 16 Jul 2015 14:57:44 +0300 Subject: [PATCH 090/369] Add attributes to TRange. Also make attributes an OrderedDict, for stable order during LLVM IR generation. --- artiq/compiler/builtins.py | 5 +++++ artiq/compiler/types.py | 9 +++++---- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/artiq/compiler/builtins.py b/artiq/compiler/builtins.py index 5fdd49708..4675c458c 100644 --- a/artiq/compiler/builtins.py +++ b/artiq/compiler/builtins.py @@ -36,6 +36,11 @@ class TRange(types.TMono): 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): def __init__(self): diff --git a/artiq/compiler/types.py b/artiq/compiler/types.py index 4cff68161..5e69c399f 100644 --- a/artiq/compiler/types.py +++ b/artiq/compiler/types.py @@ -4,6 +4,7 @@ in :mod:`asttyped`. """ import string +from collections import OrderedDict def genalnum(): ident = ["a"] @@ -80,7 +81,7 @@ class TMono(Type): unlike all other :class:`Type` descendants. """ - attributes = {} + attributes = OrderedDict() def __init__(self, name, params={}): self.name, self.params = name, params @@ -124,7 +125,7 @@ class TTuple(Type): :ivar elts: (list of :class:`Type`) elements """ - attributes = {} + attributes = OrderedDict() def __init__(self, elts=[]): self.elts = elts @@ -168,7 +169,7 @@ class TFunction(Type): return type """ - attributes = {} + attributes = OrderedDict() def __init__(self, args, optargs, ret): self.args, self.optargs, self.ret = args, optargs, ret @@ -220,7 +221,7 @@ class TBuiltin(Type): def __init__(self, name): self.name = name - self.attributes = {} + self.attributes = OrderedDict() def find(self): return self From c1e7a82e975d97e054b769d615c36efb287d7488 Mon Sep 17 00:00:00 2001 From: whitequark Date: Thu, 16 Jul 2015 14:58:40 +0300 Subject: [PATCH 091/369] Add IndexError and ValueError builtins. --- artiq/compiler/builtins.py | 16 +++++++++++++++- artiq/compiler/prelude.py | 2 ++ artiq/compiler/transforms/inferencer.py | 20 +++++++++++++++++++- 3 files changed, 36 insertions(+), 2 deletions(-) diff --git a/artiq/compiler/builtins.py b/artiq/compiler/builtins.py index 4675c458c..c349baac5 100644 --- a/artiq/compiler/builtins.py +++ b/artiq/compiler/builtins.py @@ -43,8 +43,16 @@ class TRange(types.TMono): ]) class TException(types.TMono): + def __init__(self, name="Exception"): + super().__init__(name) + +class TIndexError(types.TMono): def __init__(self): - super().__init__("Exception") + super().__init__("IndexError") + +class TValueError(types.TMono): + def __init__(self): + super().__init__("ValueError") def fn_bool(): return types.TConstructor("bool") @@ -61,6 +69,12 @@ def fn_list(): def fn_Exception(): return types.TExceptionConstructor("Exception") +def fn_IndexError(): + return types.TExceptionConstructor("IndexError") + +def fn_ValueError(): + return types.TExceptionConstructor("ValueError") + def fn_range(): return types.TBuiltinFunction("range") diff --git a/artiq/compiler/prelude.py b/artiq/compiler/prelude.py index d024c2423..98432b753 100644 --- a/artiq/compiler/prelude.py +++ b/artiq/compiler/prelude.py @@ -13,6 +13,8 @@ def globals(): "list": builtins.fn_list(), "range": builtins.fn_range(), "Exception": builtins.fn_Exception(), + "IndexError": builtins.fn_IndexError(), + "ValueError": builtins.fn_ValueError(), "len": builtins.fn_len(), "round": builtins.fn_round(), "syscall": builtins.fn_syscall(), diff --git a/artiq/compiler/transforms/inferencer.py b/artiq/compiler/transforms/inferencer.py index 5400da937..9e927d60f 100644 --- a/artiq/compiler/transforms/inferencer.py +++ b/artiq/compiler/transforms/inferencer.py @@ -432,7 +432,25 @@ class Inferencer(algorithm.Visitor): node.func.loc, notes=valid_forms) self.engine.process(diag) - if types.is_builtin(typ, "bool"): + if types.is_exn_constructor(typ): + exns = { + "IndexError": builtins.TIndexError, + "ValueError": builtins.TValueError, + } + for exn in exns: + if types.is_exn_constructor(typ, exn): + valid_forms = lambda: [ + valid_form("{exn}() -> {exn}".format(exn=exn)) + ] + + if len(node.args) == 0 and len(node.keywords) == 0: + pass # False + else: + diagnose(valid_forms()) + + self._unify(node.type, exns[exn](), + node.loc, None) + elif types.is_builtin(typ, "bool"): valid_forms = lambda: [ valid_form("bool() -> bool"), valid_form("bool(x:'a) -> bool") From 6cda67c0c68c14ddcad643742a19e165079acafc Mon Sep 17 00:00:00 2001 From: whitequark Date: Thu, 16 Jul 2015 14:59:05 +0300 Subject: [PATCH 092/369] Ensure type comparisons see through type variables. --- artiq/compiler/types.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/artiq/compiler/types.py b/artiq/compiler/types.py index 5e69c399f..4b6dd9a55 100644 --- a/artiq/compiler/types.py +++ b/artiq/compiler/types.py @@ -26,6 +26,13 @@ class UnificationError(Exception): def __init__(self, typea, typeb): self.typea, self.typeb = typea, typeb +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): pass @@ -113,7 +120,7 @@ class TMono(Type): def __eq__(self, other): return isinstance(other, TMono) and \ self.name == other.name and \ - self.params == other.params + _map_find(self.params) == _map_find(other.params) def __ne__(self, other): return not (self == other) @@ -152,7 +159,7 @@ class TTuple(Type): def __eq__(self, other): return isinstance(other, TTuple) and \ - self.elts == other.elts + _map_find(self.elts) == _map_find(other.elts) def __ne__(self, other): return not (self == other) @@ -207,8 +214,8 @@ class TFunction(Type): def __eq__(self, other): return isinstance(other, TFunction) and \ - self.args == other.args and \ - self.optargs == other.optargs + _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) From 5756cfcebcbd1739677d02e4b29de4ab0d1eff60 Mon Sep 17 00:00:00 2001 From: whitequark Date: Thu, 16 Jul 2015 15:35:46 +0300 Subject: [PATCH 093/369] Correctly infer type of list(iterable). --- artiq/compiler/transforms/inferencer.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/artiq/compiler/transforms/inferencer.py b/artiq/compiler/transforms/inferencer.py index 9e927d60f..a8afa8a9a 100644 --- a/artiq/compiler/transforms/inferencer.py +++ b/artiq/compiler/transforms/inferencer.py @@ -534,7 +534,20 @@ class Inferencer(algorithm.Visitor): arg, = node.args if builtins.is_iterable(arg.type): - pass + 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: From bcd18322035d7f9272e1ed1d0050fede6aa0c665 Mon Sep 17 00:00:00 2001 From: whitequark Date: Thu, 16 Jul 2015 15:59:59 +0300 Subject: [PATCH 094/369] Ensure bindings are created in correct order for e.g. "x, y = y, x". --- artiq/compiler/transforms/asttyped_rewriter.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/artiq/compiler/transforms/asttyped_rewriter.py b/artiq/compiler/transforms/asttyped_rewriter.py index 094bce534..13527ba52 100644 --- a/artiq/compiler/transforms/asttyped_rewriter.py +++ b/artiq/compiler/transforms/asttyped_rewriter.py @@ -107,12 +107,25 @@ class LocalExtractor(algorithm.Visitor): def visit_Name(self, node): if self.in_assign: - # code like: + # Code like: # x = 1 # def f(): # x = 1 # creates a new binding for x in f's scope self._assignable(node.id) + else: + # This is duplicated here as well as below so that + # code like: + # x, y = y, x + # where y and x were not defined earlier would be invalid. + if node.id in self.typing_env: + return + for outer_env in reversed(self.env_stack): + if node.id in outer_env: + return + diag = diagnostic.Diagnostic("fatal", + "name '{name}' is not bound to anything", {"name":node.id}, node.loc) + self.engine.process(diag) def visit_Attribute(self, node): self.visit_in_assign(node.value, in_assign=False) From f8e51e07d57ad8e1b0cc2b307248cef8ada622f8 Mon Sep 17 00:00:00 2001 From: whitequark Date: Thu, 16 Jul 2015 16:03:24 +0300 Subject: [PATCH 095/369] Add zero/one accessors to TBool, TInt, TFloat. --- artiq/compiler/builtins.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/artiq/compiler/builtins.py b/artiq/compiler/builtins.py index c349baac5..4e4406925 100644 --- a/artiq/compiler/builtins.py +++ b/artiq/compiler/builtins.py @@ -3,6 +3,7 @@ The :mod:`builtins` module contains the builtin Python and ARTIQ types, such as int or float. """ +from collections import OrderedDict from . import types # Types @@ -15,16 +16,40 @@ 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 + class TFloat(types.TMono): def __init__(self): super().__init__("float") + @staticmethod + def zero(): + return 0.0 + + @staticmethod + def one(): + return 1.0 + class TList(types.TMono): def __init__(self, elt=None): if elt is None: From 2dcb7445193f844c183a51155b9718683d46bcce Mon Sep 17 00:00:00 2001 From: whitequark Date: Thu, 16 Jul 2015 17:26:31 +0300 Subject: [PATCH 096/369] Fix inference for default arguments. --- artiq/compiler/transforms/inferencer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/artiq/compiler/transforms/inferencer.py b/artiq/compiler/transforms/inferencer.py index a8afa8a9a..3db7e12c0 100644 --- a/artiq/compiler/transforms/inferencer.py +++ b/artiq/compiler/transforms/inferencer.py @@ -852,7 +852,7 @@ class Inferencer(algorithm.Visitor): def visit_arguments(self, node): self.generic_visit(node) - for arg, default in zip(node.args[len(node.defaults):], node.defaults): + 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) From 3b661b2b6501cfa4e8633f9e74c7493ecd0475b8 Mon Sep 17 00:00:00 2001 From: whitequark Date: Fri, 17 Jul 2015 16:04:46 +0300 Subject: [PATCH 097/369] Fix environment corruption by ExceptHandler without a name. --- artiq/compiler/transforms/asttyped_rewriter.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/artiq/compiler/transforms/asttyped_rewriter.py b/artiq/compiler/transforms/asttyped_rewriter.py index 13527ba52..244f99fd0 100644 --- a/artiq/compiler/transforms/asttyped_rewriter.py +++ b/artiq/compiler/transforms/asttyped_rewriter.py @@ -92,6 +92,7 @@ class LocalExtractor(algorithm.Visitor): 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() @@ -177,7 +178,8 @@ class LocalExtractor(algorithm.Visitor): def visit_ExceptHandler(self, node): self.visit(node.type) - self._assignable(node.name) + if node.name is not None: + self._assignable(node.name) for stmt in node.body: self.visit(stmt) @@ -383,8 +385,12 @@ class ASTTypedRewriter(algorithm.Transformer): 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=self._find_name(node.name, node.name_loc), + 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) From f28549a11a5fbee34da7a5b9e7eae443e200e754 Mon Sep 17 00:00:00 2001 From: whitequark Date: Fri, 17 Jul 2015 16:05:02 +0300 Subject: [PATCH 098/369] Add builtins.is_exception. --- artiq/compiler/builtins.py | 9 ++++++--- artiq/compiler/transforms/inferencer.py | 2 +- artiq/compiler/types.py | 3 --- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/artiq/compiler/builtins.py b/artiq/compiler/builtins.py index 4e4406925..8ca095b81 100644 --- a/artiq/compiler/builtins.py +++ b/artiq/compiler/builtins.py @@ -71,11 +71,11 @@ class TException(types.TMono): def __init__(self, name="Exception"): super().__init__(name) -class TIndexError(types.TMono): +class TIndexError(TException): def __init__(self): super().__init__("IndexError") -class TValueError(types.TMono): +class TValueError(TException): def __init__(self): super().__init__("ValueError") @@ -150,6 +150,9 @@ def is_range(typ, elt=None): else: return types.is_mono(typ, "range") +def is_exception(typ): + return isinstance(typ.find(), TException) + def is_iterable(typ): typ = typ.find() return isinstance(typ, types.TMono) and \ @@ -157,7 +160,7 @@ def is_iterable(typ): def get_iterable_elt(typ): if is_iterable(typ): - return typ.find()["elt"] + return typ.find()["elt"].find() def is_collection(typ): typ = typ.find() diff --git a/artiq/compiler/transforms/inferencer.py b/artiq/compiler/transforms/inferencer.py index 3db7e12c0..2e34c4e57 100644 --- a/artiq/compiler/transforms/inferencer.py +++ b/artiq/compiler/transforms/inferencer.py @@ -827,7 +827,7 @@ class Inferencer(algorithm.Visitor): {"typeb": printer.name(typeb)}, locb) ] - self._unify(node.name_type, node.filter.type.to_exception_type(), + self._unify(node.name_type, builtins.TException(node.filter.type.name), node.name_loc, node.filter.loc, makenotes) def _type_from_arguments(self, node, ret): diff --git a/artiq/compiler/types.py b/artiq/compiler/types.py index 4b6dd9a55..b29eb513e 100644 --- a/artiq/compiler/types.py +++ b/artiq/compiler/types.py @@ -268,9 +268,6 @@ class TExceptionConstructor(TBuiltin): Note that this is not the same as the type of an instance of the class, which is ``TMono("Exception", ...)``. """ - def to_exception_type(self): - # Exceptions currently can't have type parameters - return TMono(self.name, {}) class TValue(Type): """ From e96bc3c36c91a8b8d8c7801197cd20a3c0f724a2 Mon Sep 17 00:00:00 2001 From: whitequark Date: Fri, 17 Jul 2015 21:29:06 +0300 Subject: [PATCH 099/369] Add complete IR generator. --- artiq/compiler/ir.py | 615 +++++++++++- artiq/compiler/transforms/ir_generator.py | 1086 ++++++++++++++++++++- 2 files changed, 1614 insertions(+), 87 deletions(-) diff --git a/artiq/compiler/ir.py b/artiq/compiler/ir.py index 5f6688759..9e04c0231 100644 --- a/artiq/compiler/ir.py +++ b/artiq/compiler/ir.py @@ -3,6 +3,8 @@ The :mod:`ir` module contains the intermediate representation of the ARTIQ compiler. """ +from collections import OrderedDict +from pythonparser import ast from . import types, builtins # Generic SSA IR classes @@ -13,13 +15,13 @@ def escape_name(name): else: return "\"{}\"".format(name.replace("\"", "\\\"")) -class TSSABasicBlock(types.TMono): +class TBasicBlock(types.TMono): def __init__(self): - super().__init__("ssa.basic_block") + super().__init__("label") -class TSSAOption(types.TMono): +class TOption(types.TMono): def __init__(self, inner): - super().__init__("ssa.option", {"inner": inner}) + super().__init__("option", {"inner": inner}) class Value: """ @@ -30,7 +32,7 @@ class Value: """ def __init__(self, typ): - self.uses, self.type = set(), typ + self.uses, self.type = set(), typ.find() def replace_all_uses_with(self, value): for user in self.uses: @@ -40,7 +42,7 @@ class Constant(Value): """ A constant value. - :ivar value: (None, True or False) value + :ivar value: (Python object) value """ def __init__(self, value, typ): @@ -127,6 +129,8 @@ class Instruction(User): """ def __init__(self, operands, typ, name=""): + assert isinstance(operands, list) + assert isinstance(typ, types.Type) super().__init__(operands, typ, name) self.basic_block = None @@ -160,6 +164,9 @@ class Instruction(User): else: self.erase() + def _operands_as_string(self): + return ", ".join([operand.as_operand() for operand in self.operands]) + def __str__(self): if builtins.is_none(self.type): prefix = "" @@ -168,8 +175,7 @@ class Instruction(User): types.TypePrinter().name(self.type)) if any(self.operands): - return "{}{} {}".format(prefix, self.opcode(), - ", ".join([operand.as_operand() for operand in self.operands])) + return "{}{} {}".format(prefix, self.opcode(), self._operands_as_string()) else: return "{}{}".format(prefix, self.opcode()) @@ -182,7 +188,7 @@ class Phi(Instruction): """ def __init__(self, typ, name=""): - super().__init__(typ, name) + super().__init__([], typ, name) def opcode(self): return "phi" @@ -217,9 +223,11 @@ class Phi(Instruction): types.TypePrinter().name(self.type)) if any(self.operands): - operand_list = ["%{} => %{}".format(escape_name(block.name), escape_name(value.name)) - for operand in self.operands] + operand_list = ["%{} => {}".format(escape_name(block.name), value.as_operand()) + for value, block in self.incoming()] return "{}{} [{}]".format(prefix, self.opcode(), ", ".join(operand_list)) + else: + return "{}{} [???]".format(prefix, self.opcode()) class Terminator(Instruction): """ @@ -237,7 +245,7 @@ class BasicBlock(NamedValue): """ def __init__(self, instructions, name=""): - super().__init__(TSSABasicBlock(), name) + super().__init__(TBasicBlock(), name) self.instructions = [] self.set_instructions(instructions) @@ -253,10 +261,13 @@ class BasicBlock(NamedValue): self.function.remove(self) 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 @@ -265,11 +276,13 @@ class BasicBlock(NamedValue): return self.instructions.index(insn) def insert(self, before, insn): + 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 @@ -290,10 +303,13 @@ class BasicBlock(NamedValue): def predecessors(self): assert self.function is not None - self.function.predecessors_of(self) + return self.function.predecessors_of(self) def __str__(self): lines = ["{}:".format(escape_name(self.name))] + if self.function is not None: + lines[0] += " ; predecessors: {}".format( + ", ".join([escape_name(pred.name) for pred in self.predecessors()])) for insn in self.instructions: lines.append(" " + str(insn)) return "\n".join(lines) @@ -312,10 +328,9 @@ class Function(Value): """ def __init__(self, typ, name, arguments): - self.type, self.name = typ, name - self.arguments = [] - self.basic_blocks = [] - self.names = set() + super().__init__(typ) + self.name = name + self.names, self.arguments, self.basic_blocks = set(), [], [] self.set_arguments(arguments) def _remove_name(self, name): @@ -325,7 +340,7 @@ class Function(Value): name, counter = base_name, 1 while name in self.names or name == "": if base_name == "": - name = str(counter) + name = "v.{}".format(str(counter)) else: name = "{}.{}".format(name, counter) counter += 1 @@ -346,10 +361,11 @@ class Function(Value): def remove(self, basic_block): basic_block._detach() - self.basic_block.remove(basic_block) + self.basic_blocks.remove(basic_block) def predecessors_of(self, successor): - return [block for block in self.basic_blocks if successor in block.successors()] + return [block for block in self.basic_blocks + if block.is_terminated() and successor in block.successors()] def as_operand(self): return "{} @{}".format(types.TypePrinter().name(self.type), @@ -369,6 +385,412 @@ class Function(Value): # Python-specific SSA IR classes +class TEnvironment(types.TMono): + def __init__(self, vars, outer=None): + 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 + + """ + 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(NamedValue): + """ + A function argument specifying an outer environment. + """ + + def as_operand(self): + 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): + if is_environment(self.type): + # Only show full environment in the instruction itself + return "%{}".format(escape_name(self.name)) + else: + return super().as_operand() + +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, Alloc) + 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 opcode(self): + return "getlocal({})".format(self.var_name) + + def get_env(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, Alloc) + 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 opcode(self): + return "setlocal({})".format(self.var_name) + + def get_env(self): + return self.operands[0] + + def get_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] + else: + typ = obj.type.attributes[attr] + super().__init__([obj], typ, name) + self.attr = attr + + def opcode(self): + return "getattr({})".format(repr(self.attr)) + + def get_env(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] + else: + assert value.type == obj.type.attributes[attr] + super().__init__([obj, value], typ, name) + self.attr = attr + + def opcode(self): + return "setattr({})".format(repr(self.attr)) + + def get_env(self): + return self.operands[0] + + def get_value(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 get_list(self): + return self.operands[0] + + def get_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 get_list(self): + return self.operands[0] + + def get_index(self): + return self.operands[1] + + def get_value(self): + return self.operands[2] + +class UnaryOp(Instruction): + """ + An unary operation on numbers. + + :ivar op: (:class:`pythonparser.ast.unaryop`) operation + """ + + """ + :param op: (:class:`pythonparser.ast.unaryop`) operation + :param operand: (:class:`Value`) operand + """ + def __init__(self, op, operand, name=""): + assert isinstance(op, ast.unaryop) + assert isinstance(operand, Value) + super().__init__([operand], operand.type, name) + self.op = op + + def opcode(self): + return "unaryop({})".format(type(self.op).__name__) + + def get_operand(self): + return self.operands[0] + +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 get_value(self): + return self.operands[0] + +class BinaryOp(Instruction): + """ + A binary operation on numbers. + + :ivar op: (:class:`pythonparser.ast.unaryop`) 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 opcode(self): + return "binaryop({})".format(type(self.op).__name__) + + def get_lhs(self): + return self.operands[0] + + def get_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 opcode(self): + return "compare({})".format(type(self.op).__name__) + + def get_lhs(self): + return self.operands[0] + + def get_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=""): + assert isinstance(op, str) + for operand in operands: assert isinstance(operand, Value) + super().__init__(operands, typ, name) + self.op = op + + def opcode(self): + return "builtin({})".format(self.op) + +class Call(Instruction): + """ + A function call operation. + """ + + """ + :param func: (:class:`Value`) function to call + :param args: (list of :class:`Value`) function arguments + """ + def __init__(self, func, args, name=""): + print(func) + assert isinstance(func, Value) + for arg in args: assert isinstance(arg, Value) + super().__init__([func] + args, func.type.ret, name) + + def opcode(self): + return "call" + + def get_function(self): + return self.operands[0] + + def get_arguments(self): + return self.operands[1:] + +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 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 get_condition(self): + return self.operands[0] + + def get_if_true(self): + return self.operands[1] + + def get_if_false(self): + return self.operands[2] + class Branch(Terminator): """ An unconditional branch instruction. @@ -378,12 +800,13 @@ class Branch(Terminator): :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): + def get_target(self): return self.operands[0] class BranchIf(Terminator): @@ -393,24 +816,57 @@ class BranchIf(Terminator): """ :param cond: (:class:`Value`) branch condition - :param if_true: (:class:`BasicBlock`) branch target if expression is truthful - :param if_false: (:class:`BasicBlock`) branch target if expression is falseful + :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 isinstance(if_true, BasicBlock) + assert isinstance(if_false, BasicBlock) super().__init__([cond, if_true, if_false], builtins.TNone(), name) def opcode(self): - return "branch_if" + return "branchif" - def condition(self): + def get_condition(self): return self.operands[0] - def if_true(self): + def get_if_true(self): return self.operands[1] - def if_false(self): + def get_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 get_target(self): + return self.operands[0] + + def get_destinations(self): + return self.operands[1:] + + def add_destination(self, destination): + self.operands.append(destination) + + def _operands_as_string(self): + return "{}, [{}]".format(self.operands[0].as_operand(), + ", ".join([dest.as_operand() for dest in self.operands[1:]])) + class Return(Terminator): """ A return instruction. @@ -420,28 +876,113 @@ class Return(Terminator): :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): + def get_value(self): return self.operands[0] -class Eval(Instruction): +class Unreachable(Terminator): """ - An instruction that evaluates an AST fragment. + An instruction used to mark unreachable branches. """ """ - :param ast: (:class:`.asttyped.AST`) return value + :param target: (:class:`BasicBlock`) branch target """ - def __init__(self, ast, name=""): - super().__init__([], ast.type, name) - self.ast = ast + def __init__(self, name=""): + super().__init__([], builtins.TNone(), name) def opcode(self): - return "eval" + return "unreachable" - def __str__(self): - return super().__str__() + " `{}`".format(self.ast.loc.source()) +class Raise(Terminator): + """ + A raise instruction. + """ + + """ + :param value: (:class:`Value`) exception value + """ + def __init__(self, value, name=""): + assert isinstance(value, Value) + super().__init__([value], builtins.TNone(), name) + + def opcode(self): + return "raise" + + def get_value(self): + return self.operands[0] + +class Invoke(Terminator): + """ + A function call operation that supports exception handling. + """ + + """ + :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 + """ + def __init__(self, func, args, normal, exn, name=""): + assert isinstance(func, Value) + for arg in args: assert isinstance(arg, Value) + assert isinstance(normal, BasicBlock) + assert isinstance(exn, BasicBlock) + super().__init__([func] + args + [normal, exn], func.type.ret, name) + + def opcode(self): + return "invoke" + + def get_function(self): + return self.operands[0] + + def get_arguments(self): + return self.operands[1:-2] + + def get_normal_target(self): + return self.operands[-2] + + def get_exception_target(self): + return self.operands[-1] + + def _operands_as_string(self): + result = ", ".join([operand.as_operand() for operand in self.operands[:-2]]) + result += " to {} unwind {}".format(self.operands[-2].as_operand(), + self.operands[-1].as_operand()) + 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, name=""): + super().__init__([], builtins.TException(), name) + self.types = [] + + def add_clause(self, target, typ): + assert isinstance(target, BasicBlock) + assert builtins.is_exception(typ) + self.operands.append(target) + self.types.append(typ.find()) + + def opcode(self): + return "landingpad" + + def _operands_as_string(self): + table = [] + for typ, target in zip(self.types, self.operands): + table.append("{} => {}".format(types.TypePrinter().name(typ), target.as_operand())) + return "[{}]".format(", ".join(table)) diff --git a/artiq/compiler/transforms/ir_generator.py b/artiq/compiler/transforms/ir_generator.py index a26d413cf..4ee4e4ddd 100644 --- a/artiq/compiler/transforms/ir_generator.py +++ b/artiq/compiler/transforms/ir_generator.py @@ -1,26 +1,72 @@ """ :class:`IRGenerator` transforms typed AST into ARTIQ intermediate -representation. +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 from pythonparser import algorithm, diagnostic, ast from .. import types, builtins, ir +def _readable_name(insn): + if isinstance(insn, ir.Constant): + return str(insn.value) + else: + return insn.name + # 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 IRGenerator(algorithm.Visitor): + """ + :class:`IRGenerator` 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_function: (:class:`ir.Function` or None) + def or lambda currently being translated + :ivar current_block: (:class:`ir.BasicBlock`) + basic block to which any new instruction will be appended + :ivar current_env: (:class:`ir.Environment`) + the chained function environment, containing variables that + can become upvalues + :ivar current_private_env: (:class:`ir.Environment`) + the private function environment, containing internal state + :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 + """ + + _size_type = builtins.TInt(types.TValue(32)) + def __init__(self, module_name, engine): self.engine = engine self.functions = [] self.name = [module_name] self.current_function = None self.current_block = None - self.break_target, self.continue_target = None, None + self.current_env = None + self.current_private_env = None + self.current_assign = None + self.break_target = None + self.continue_target = None + self.return_target = None + self.unwind_target = None - def add_block(self): - block = ir.BasicBlock([]) + def add_block(self, name=""): + block = ir.BasicBlock([], name) self.current_function.add(block) return block @@ -30,6 +76,10 @@ class IRGenerator(algorithm.Visitor): def terminate(self, insn): if not self.current_block.is_terminated(): self.append(insn) + else: + insn.drop_references() + + # Visitors def visit(self, obj): if isinstance(obj, list): @@ -40,56 +90,148 @@ class IRGenerator(algorithm.Visitor): elif isinstance(obj, ast.AST): return self._visit_one(obj) - def visit_function(self, name, typ, inner): + # Module visitor + + def visit_ModuleT(self, node): try: - old_name, self.name = self.name, self.name + [name] - - args = [] - for arg_name in typ.args: - args.append(ir.Argument(typ.args[arg_name], arg_name)) - for arg_name in typ.optargs: - args.append(ir.Argument(ir.TSSAOption(typ.optargs[arg_name]), arg_name)) - - func = ir.Function(typ, ".".join(self.name), args) + typ = types.TFunction(OrderedDict(), OrderedDict(), builtins.TNone()) + func = ir.Function(typ, ".".join(self.name + ['__modinit__']), []) self.functions.append(func) old_func, self.current_function = self.current_function, func - self.current_block = self.add_block() - inner() + entry = self.add_block("entry") + old_block, self.current_block = self.current_block, entry + + env = self.append(ir.Alloc([], ir.TEnvironment(node.typing_env), name="env")) + old_env, self.current_env = self.current_env, env + + priv_env = self.append(ir.Alloc([], ir.TEnvironment({ ".return": typ.ret }))) + 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 func + 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_function(self, node, is_lambda): + 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 = [] + 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)) + defaults.append(env_default_name) + + old_name, self.name = self.name, self.name + [name] + + env_arg = ir.EnvironmentArgument(self.current_env.type, "outerenv") + + args = [] + for arg_name in typ.args: + args.append(ir.Argument(typ.args[arg_name], "arg." + arg_name)) + + optargs = [] + for arg_name in typ.optargs: + optargs.append(ir.Argument(ir.TSSAOption(typ.optargs[arg_name]), "arg." + arg_name)) + + func = ir.Function(typ, ".".join(self.name), [env_arg] + args + optargs) + self.functions.append(func) + old_func, self.current_function = self.current_function, func + + entry = self.add_block() + old_block, self.current_block = self.current_block, entry + + env_type = ir.TEnvironment(node.typing_env, 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 = self.append(ir.Alloc([], ir.TEnvironment({ ".return": typ.ret }))) + 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, env_default_name) in enumerate(zip(typ.optargs, defaults)): + default = self.append(ir.GetLocal(self.current_env, env_default_name)) + value = self.append(ir.Builtin("unwrap", [optargs[index], default], + typ.optargs[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): + self.terminate(ir.Return(ir.Constant(None, builtins.TNone()))) + else: + self.terminate(ir.Unreachable()) + + return func finally: self.name = old_name self.current_function = old_func - - def visit_ModuleT(self, node): - def inner(): - self.generic_visit(node) - - return_value = ir.Constant(None, builtins.TNone()) - self.terminate(ir.Return(return_value)) - - typ = types.TFunction(OrderedDict(), OrderedDict(), builtins.TNone()) - self.visit_function('__modinit__', typ, inner) + self.current_block = old_block + self.current_env = old_env + if not is_lambda: + self.current_private_env = old_priv_env def visit_FunctionDefT(self, node): - self.visit_function(node.name, node.signature_type.find(), - lambda: self.generic_visit(node)) + func = self.visit_function(node, is_lambda=False) + self.append(ir.SetLocal(self.current_env, node.name, func)) 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: - expr = self.append(ir.Eval(node.value)) - self.append(ir.Return(expr)) + self.append(ir.SetLocal(self.current_private_env, ".return", return_value)) + self.append(ir.Branch(self.return_target)) def visit_Expr(self, node): - self.append(ir.Eval(node.value)) + # ignore the value, do it for side effects + self.visit(node.value) - # Assign - # AugAssign + 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(target) + rhs = self.visit(node.value) + value = self.append(ir.BinaryOp(node.op, lhs, rhs)) + try: + self.current_assign = value + self.visit(node.target) + finally: + self.current_assign = None def visit_If(self, node): - cond = self.append(ir.Eval(node.test)) + cond = self.visit(node.test) head = self.current_block if_true = self.add_block() @@ -110,32 +252,120 @@ class IRGenerator(algorithm.Visitor): def visit_While(self, node): try: - head = self.add_block() + 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) - tail_tramp = self.add_block() - old_break, self.break_target = self.break_target, tail_tramp + break_block = self.add_block("while.break") + old_break, self.break_target = self.break_target, break_block - body = self.add_block() - old_continue, self.continue_target = self.continue_target, body + body = self.add_block("while.body") self.current_block = body self.visit(node.body) - tail = self.add_block() - self.current_block = tail - self.visit(node.orelse) + if any(node.orelse): + else_tail = self.add_block("while.else") + self.current_block = else_tail + self.visit(node.orelse) - cond = head.append(ir.Eval(node.test)) - head.append(ir.BranchIf(cond, body, tail)) + 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)) + else: + else_tail = tail + + head.append(ir.BranchIf(cond, body, else_tail)) if not body.is_terminated(): - body.append(ir.Branch(tail)) - tail_tramp.append(ir.Branch(tail)) + body.append(ir.Branch(head)) + break_block.append(ir.Branch(tail)) finally: self.break_target = old_break self.continue_target = old_continue - # For + def _iterable_len(self, value, typ=builtins.TInt(types.TValue(32))): + if builtins.is_list(value.type): + return self.append(ir.Builtin("len", [value], 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.BinaryOp(ast.Sub(loc=None), stop, start)) + return self.append(ir.BinaryOp(ast.FloorDiv(loc=None), spread, step)) + else: + assert False + + def _iterable_get(self, value, index): + # Assuming the value is within bounds. + if builtins.is_list(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.BinaryOp(ast.Mult(loc=None), step, index)) + return self.append(ir.BinaryOp(ast.Add(loc=None), start, offset)) + else: + assert False + + def visit_For(self, node): + try: + iterable = self.visit(node.iter) + length = self._iterable_len(iterable) + + head = self.add_block("for.head") + self.append(ir.Branch(head)) + self.current_block = head + phi = self.append(ir.Phi(length.type)) + phi.add_incoming(ir.Constant(0, phi.type), head) + cond = self.append(ir.Compare(ast.Lt(loc=None), phi, length)) + + 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.BinaryOp(ast.Add(loc=None), phi, + ir.Constant(1, phi.type))) + 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.iter) + finally: + self.current_assign = None + self.visit(node.body) + + if any(node.orelse): + else_tail = self.add_block("for.else") + self.current_block = else_tail + self.visit(node.orelse) + + 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)) + else: + else_tail = tail + + head.append(ir.BranchIf(cond, body, else_tail)) + if not body.is_terminated(): + body.append(ir.Branch(continue_block)) + break_block.append(ir.Branch(tail)) + finally: + self.break_target = old_break + self.continue_target = old_continue def visit_Break(self, node): self.append(ir.Branch(self.break_target)) @@ -143,7 +373,763 @@ class IRGenerator(algorithm.Visitor): def visit_Continue(self, node): self.append(ir.Branch(self.continue_target)) - # Raise - # Try + def visit_Raise(self, node): + self.append(ir.Raise(self.visit(node.exc))) - # With + def visit_Try(self, node): + dispatcher = self.add_block("try.dispatch") + landingpad = dispatcher.append(ir.LandingPad()) + + if any(node.finalbody): + # k for continuation + final_state = self.append(ir.Alloc([], ir.TEnvironment({ "k": ir.TBasicBlock() }))) + final_targets = [] + + if self.break_target is not None: + break_proxy = self.add_block("try.break") + old_break, self.break_target = self.break_target, break_proxy + break_proxy.append(ir.SetLocal(final_state, "k", old_break)) + final_targets.append(old_break) + if self.continue_target is not None: + continue_proxy = self.add_block("try.continue") + old_continue, self.continue_target = self.continue_target, continue_proxy + continue_proxy.append(ir.SetLocal(final_state, "k", old_continue)) + final_targets.append(old_continue) + + return_proxy = self.add_block("try.return") + old_return, self.return_target = self.return_target, return_proxy + if old_return is not None: + return_proxy.append(ir.SetLocal(final_state, "k", old_return)) + final_targets.append(old_return) + 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)) + return_proxy.append(ir.SetLocal(final_state, "k", return_action)) + final_targets.append(return_action) + + 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 + + self.visit(node.orelse) + 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 + + handlers = [] + for handler_node in node.handlers: + handler = self.add_block("handler." + handler_node.name_type.find().name) + self.current_block = handler + handlers.append(handler) + landingpad.add_clause(handler, handler_node.name_type) + + if handler_node.name is not None: + exn = self.append(ir.Builtin("exncast", [landingpad], handler_node.name_type)) + self.append(ir.SetLocal(self.current_env, handler_node.name, exn)) + self.visit(handler_node.body) + + if any(node.finalbody): + finalizer = self.add_block("finally") + self.current_block = finalizer + + self.visit(node.finalbody) + + if not self.current_block.is_terminated(): + dest = self.append(ir.GetLocal(final_state, "k")) + self.append(ir.IndirectBranch(dest, final_targets)) + + tail = self.add_block("try.tail") + if any(node.finalbody): + if self.break_target: + break_proxy.append(ir.Branch(finalizer)) + if self.continue_target: + continue_proxy.append(ir.Branch(finalizer)) + return_proxy.append(ir.Branch(finalizer)) + if not body.is_terminated(): + if any(node.finalbody): + body.append(ir.SetLocal(final_state, "k", tail)) + body.append(ir.Branch(finalizer)) + for handler in handlers: + if not handler.is_terminated(): + handler.append(ir.SetLocal(final_state, "k", tail)) + handler.append(ir.Branch(tail)) + else: + body.append(ir.Branch(tail)) + for handler in handlers: + if not handler.is_terminated(): + handler.append(ir.Branch(tail)) + + if any(tail.predecessors()): + self.current_block = tail + else: + self.current_function.remove(tail) + + # TODO: 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) + + def visit_IfExpT(self, node): + cond = self.visit(node.test) + head = self.current_block + + if_true = self.add_block() + self.current_block = if_true + true_result = self.visit(node.body) + + if_false = self.add_block() + self.current_block = if_false + false_result = self.visit(node.orelse) + + tail = self.add_block() + self.current_block = tail + + if not if_true.is_terminated(): + if_true.append(ir.Branch(tail)) + if not if_false.is_terminated(): + 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, if_true) + phi.add_incoming(false_result, if_false) + return phi + + def visit_NumT(self, node): + return ir.Constant(node.n, node.type) + + def visit_NameConstantT(self, node): + return ir.Constant(node.value, node.type) + + def visit_NameT(self, node): + if self.current_assign is None: + return self.append(ir.GetLocal(self.current_env, node.id, + name="{}.{}".format(self.current_env.name, node.id))) + else: + self.append(ir.SetLocal(self.current_env, 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="{}.{}".format(_readable_name(obj), node.attr))) + else: + self.append(ir.SetAttr(obj, node.attr, self.current_assign)) + + def _map_index(self, length, index): + lt_0 = self.append(ir.Compare(ast.Lt(loc=None), + index, ir.Constant(0, index.type))) + from_end = self.append(ir.BinaryOp(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)) + in_bounds = self.append(ir.Select(mapped_ge_0, mapped_lt_len, + ir.Constant(False, builtins.TBool()))) + + out_of_bounds_block = self.add_block() + exn = out_of_bounds_block.append(ir.Alloc([], builtins.TIndexError())) + out_of_bounds_block.append(ir.Raise(exn)) + + in_bounds_block = self.add_block() + + self.append(ir.BranchIf(in_bounds, in_bounds_block, out_of_bounds_block)) + self.current_block = in_bounds_block + + return mapped_index + + def _make_check(self, cond, exn_gen): + # 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() + self.append(ir.Raise(exn_gen())) + + self.current_block = tail_block = self.add_block() + cond_block.append(ir.BranchIf(cond, tail_block, body_block)) + + def _make_loop(self, init, cond_gen, body_gen): + # 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() + 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 = body_gen(phi) + self.append(ir.Branch(head_block)) + phi.add_incoming(body, self.current_block) + + self.current_block = tail_block = self.add_block() + 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): + index = self.visit(node.slice.value) + length = self._iterable_len(value, index.type) + mapped_index = self._map_index(length, index) + if self.current_assign is None: + result = self._iterable_get(value, mapped_index) + result.set_name("{}.at.{}".format(value.name, _readable_name(index))) + return result + else: + self.append(ir.SetElem(value, mapped_index, self.current_assign, + name="{}.at.{}".format(value.name, _readable_name(index)))) + else: # Slice + length = self._iterable_len(value, node.slice.type) + + if node.slice.lower is not None: + min_index = self.visit(node.slice.lower) + else: + min_index = ir.Constant(0, node.slice.type) + mapped_min_index = self._map_index(length, min_index) + + if node.slice.upper is not None: + max_index = self.visit(node.slice.upper) + else: + max_index = length + mapped_max_index = self._map_index(length, max_index) + + if node.slice.step is not None: + step = self.visit(node.slice.step) + else: + step = ir.Constant(1, node.slice.type) + + unstepped_size = self.append(ir.BinaryOp(ast.Sub(loc=None), + mapped_max_index, mapped_min_index)) + slice_size = self.append(ir.BinaryOp(ast.FloorDiv(loc=None), unstepped_size, step)) + + self._make_check(self.append(ir.Compare(ast.Eq(loc=None), slice_size, length)), + lambda: self.append(ir.Alloc([], builtins.TValueError()))) + + if self.current_assign is None: + other_value = self.append(ir.Alloc([slice_size], value.type)) + else: + other_value = self.current_assign + + def body_gen(other_index): + offset = self.append(ir.BinaryOp(ast.Mult(loc=None), step, other_index)) + index = self.append(ir.BinaryOp(ast.Add(loc=None), min_index, offset)) + + 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)) + + return self.append(ir.BinaryOp(ast.Add(loc=None), other_index, + ir.Constant(1, node.slice.type))) + self._make_loop(ir.Constant(0, node.slice.type), + lambda index: self.append(ir.Compare(ast.Lt(loc=None), index, slice_size)), + body_gen) + + 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 in enumerate(node.elts): + self.current_assign = \ + self.append(ir.GetAttr(old_assign, index, + name="{}.{}".format(old_assign.name, + _readable_name(index)))) + self.visit(elt) + finally: + self.current_assign = old_assign + + def visit_ListT(self, node): + if self.current_assign is None: + elts = [self.visit(elt) for elt in node.elts] + lst = self.append(ir.Alloc([ir.Constant(len(node.elts), self._size_type)], + node.type)) + for index, elt in enumerate(elts): + self.append(ir.SetElem(lst, ir.Constant(index, self._size_type), elt)) + return lst + else: + length = self.append(ir.Builtin("len", [self.current_assign], self._size_type)) + 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()))) + + 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: + env_type = ir.TEnvironment(node.typing_env, 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 + print(comprehension.target, self.current_assign) + 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.BinaryOp(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 = self.visit(value_node) + blocks.append((value, self.current_block)) + self.current_block = self.add_block() + + 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 isinstance(node.op, ast.And): + block.append(ir.BranchIf(value, next_block, tail)) + else: + block.append(ir.BranchIf(value, tail, next_block)) + return phi + + def visit_UnaryOpT(self, node): + if isinstance(node.op, ast.Not): + return self.append(ir.Select(node.operand, + ir.Constant(False, builtins.TBool()), + ir.Constant(True, builtins.TBool()))) + else: # Numeric operators + return self.append(ir.UnaryOp(node.op, self.visit(node.operand))) + + def visit_CoerceT(self, node): + value = self.visit(node.value) + if node.type.find() == value.type: + return value + else: + return self.append(ir.Coerce(value, node.type, + name="{}.{}".format(_readable_name(value), + node.type.name))) + + def visit_BinOpT(self, node): + if builtins.is_numeric(node.type): + return self.append(ir.BinaryOp(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): + 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_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)) + + result_length = self.append(ir.BinaryOp(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.BinaryOp(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.BinaryOp(ast.Add(loc=None), index, lhs_length)) + self.append(ir.SetElem(result, result_index, elt)) + return self.append(ir.BinaryOp(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_list(lhs.type) and builtins.is_int(rhs.type): + lst, num = lhs, rhs + elif builtins.is_int(lhs.type) and builtins.is_list(rhs.type): + lst, num = rhs, lhs + else: + assert False + + lst_length = self.append(ir.Builtin("len", [lst], self._size_type)) + + result_length = self.append(ir.BinaryOp(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.BinaryOp(ast.Mult(loc=None), + num_index, lst_length)) + result_index = self.append(ir.BinaryOp(ast.Add(loc=None), + base_index, lst_index)) + self.append(ir.SetElem(result, base_index, elt)) + return self.append(ir.BinaryOp(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.BinaryOp(ast.Add(loc=None), lst_length, + 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) + else: + assert False + + def _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 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.append(ir.Compare(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_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)) + 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() + 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() + 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._compare_pair(op, lhs_elt, rhs_elt) + + loop_body2 = self.add_block() + self.current_block = loop_body2 + index_next = self.append(ir.BinaryOp(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() + 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) + loop_body.append(ir.BranchIf(body_result, loop_body2, tail)) + phi.add_incoming(body_result, loop_body) + + 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: + assert False + + def _compare_pair_inclusion(self, op, 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.BinaryOp(ast.Sub(loc=None), needle, start)) + mod_step = self.append(ir.BinaryOp(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._compare_pair(ast.Eq(loc=None), needle, elt) + + loop_body2 = self.add_block() + self.current_block = loop_body2 + return self.append(ir.BinaryOp(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) + + 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: + assert False + + if isinstance(op, ast.NotIn): + result = self.append(ir.Select(result, + ir.Constant(False, builtins.TBool()), + ir.Constant(True, builtins.TBool()))) + + return result + + def _compare_pair_identity(self, op, lhs, rhs): + if builtins.is_mutable(lhs) and builtins.is_mutable(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._compare_pair_order(op, lhs, rhs) + + def _compare_pair(self, op, lhs, rhs): + if isinstance(op, (ast.Is, ast.IsNot)): + return self._compare_pair_identity(op, lhs, rhs) + elif isinstance(op, (ast.In, ast.NotIn)): + return self._compare_pair_inclusion(op, lhs, rhs) + else: # Eq, NotEq, Lt, LtE, Gt, GtE + return self._compare_pair_order(op, lhs, rhs) + + 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): + rhs = self.visit(rhs_node) + result = self._compare_pair(op, lhs, rhs) + blocks.append((result, self.current_block)) + 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) + block.append(ir.BranchIf(value, next_block, tail)) + return phi + + 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.append(ir.Select(arg, + ir.Constant(True, builtins.TBool()), + ir.Constant(False, builtins.TBool()))) + else: + assert False + elif types.is_builtin(typ, "int"): + 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"): + 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)) + 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) + self.append(ir.SetElem(result, index, elt)) + return self.append(ir.BinaryOp(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, "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])) + else: + assert False + elif types.is_exn_constructor(typ): + return self.append(ir.Alloc([self.visit(arg) for args in node.args], node.type)) + else: + assert False + + def visit_CallT(self, node): + if types.is_builtin(node.func.type): + return self.visit_builtin_call(node) + else: + typ = node.func.type.find() + func = self.visit(node.func) + args = [self.visit(arg) for arg in node.args] + for index, optarg_name in enumerate(typ.optargs): + if len(typ.args) + index >= len(args): + optarg_typ = ir.TOption(typ.optargs[optarg_name]) + for keyword in node.keywords: + if keyword.arg == optarg_name: + value = self.append(ir.Alloc(optarg_typ, [self.visit(keyword.value)])) + args.append(value) + break + else: + value = self.append(ir.Alloc(optarg_typ, [])) + args.append(value) + + if self.unwind_target is None: + return self.append(ir.Call(func, args)) + else: + after_invoke = self.add_block() + invoke = self.append(ir.Invoke(func, args, after_invoke, self.unwind_target)) + self.current_block = after_invoke + return invoke From 255ffec483740ff2058f28df266dbba9135c937b Mon Sep 17 00:00:00 2001 From: whitequark Date: Sat, 18 Jul 2015 07:49:27 +0300 Subject: [PATCH 100/369] Generate more compact ARTIQ IR for else-less if. --- artiq/compiler/transforms/ir_generator.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/artiq/compiler/transforms/ir_generator.py b/artiq/compiler/transforms/ir_generator.py index 4ee4e4ddd..ef0214695 100644 --- a/artiq/compiler/transforms/ir_generator.py +++ b/artiq/compiler/transforms/ir_generator.py @@ -238,17 +238,22 @@ class IRGenerator(algorithm.Visitor): self.current_block = if_true self.visit(node.body) - if_false = self.add_block() - self.current_block = if_false - self.visit(node.orelse) + if any(node.orelse): + if_false = self.add_block() + self.current_block = if_false + self.visit(node.orelse) tail = self.add_block() self.current_block = tail if not if_true.is_terminated(): if_true.append(ir.Branch(tail)) - if not if_false.is_terminated(): - if_false.append(ir.Branch(tail)) - head.append(ir.BranchIf(cond, if_true, if_false)) + + if any(node.orelse): + if not if_false.is_terminated(): + if_false.append(ir.Branch(tail)) + head.append(ir.BranchIf(cond, if_true, if_false)) + else: + head.append(ir.BranchIf(cond, if_true, tail)) def visit_While(self, node): try: From dde2e67c3f46d7442216c31ca267f46b296784e9 Mon Sep 17 00:00:00 2001 From: whitequark Date: Sat, 18 Jul 2015 07:49:42 +0300 Subject: [PATCH 101/369] Add source locations to ARTIQ IR instructions. --- artiq/compiler/ir.py | 22 ++++++++++++++++ artiq/compiler/transforms/ir_generator.py | 31 ++++++++++++++++++++--- 2 files changed, 49 insertions(+), 4 deletions(-) diff --git a/artiq/compiler/ir.py b/artiq/compiler/ir.py index 9e04c0231..b7977cc10 100644 --- a/artiq/compiler/ir.py +++ b/artiq/compiler/ir.py @@ -133,6 +133,7 @@ class Instruction(User): assert isinstance(typ, types.Type) super().__init__(operands, typ, name) self.basic_block = None + self.loc = None def set_basic_block(self, new_basic_block): self.basic_block = new_basic_block @@ -306,12 +307,33 @@ class BasicBlock(NamedValue): return self.function.predecessors_of(self) def __str__(self): + # Header lines = ["{}:".format(escape_name(self.name))] if self.function is not None: lines[0] += " ; predecessors: {}".format( ", ".join([escape_name(pred.name) for pred in self.predecessors()])) + + # Annotated instructions + loc = None for insn in self.instructions: + if 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] + "`" + source_lines[-1][end_col:] + source_lines[0] = \ + source_lines[0][:beg_col] + "`" + 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(" " + str(insn)) + return "\n".join(lines) class Argument(NamedValue): diff --git a/artiq/compiler/transforms/ir_generator.py b/artiq/compiler/transforms/ir_generator.py index ef0214695..17372cd17 100644 --- a/artiq/compiler/transforms/ir_generator.py +++ b/artiq/compiler/transforms/ir_generator.py @@ -16,6 +16,12 @@ def _readable_name(insn): 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. @@ -25,8 +31,10 @@ class IRGenerator(algorithm.Visitor): 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) - def or lambda currently being translated + module, def or lambda currently being translated :ivar current_block: (:class:`ir.BasicBlock`) basic block to which any new instruction will be appended :ivar current_env: (:class:`ir.Environment`) @@ -55,6 +63,7 @@ class IRGenerator(algorithm.Visitor): self.engine = engine self.functions = [] self.name = [module_name] + self.current_loc = None self.current_function = None self.current_block = None self.current_env = None @@ -70,8 +79,15 @@ class IRGenerator(algorithm.Visitor): self.current_function.add(block) return block - def append(self, insn): - return self.current_block.append(insn) + 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 = self.current_loc + return block.append(insn) def terminate(self, insn): if not self.current_block.is_terminated(): @@ -88,11 +104,18 @@ class IRGenerator(algorithm.Visitor): if self.current_block.is_terminated(): break elif isinstance(obj, ast.AST): - return self._visit_one(obj) + 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__']), []) From 0d66bdfbf88f303f44619abed46d07811a5de63a Mon Sep 17 00:00:00 2001 From: whitequark Date: Sat, 18 Jul 2015 07:58:43 +0300 Subject: [PATCH 102/369] Fix For miscompilation. --- artiq/compiler/transforms/ir_generator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/artiq/compiler/transforms/ir_generator.py b/artiq/compiler/transforms/ir_generator.py index 17372cd17..ddb638c02 100644 --- a/artiq/compiler/transforms/ir_generator.py +++ b/artiq/compiler/transforms/ir_generator.py @@ -368,7 +368,7 @@ class IRGenerator(algorithm.Visitor): elt = self._iterable_get(iterable, phi) try: self.current_assign = elt - self.visit(node.iter) + self.visit(node.target) finally: self.current_assign = None self.visit(node.body) From 21eafefd28fa3d24bc03acce2f05ebfc078547a9 Mon Sep 17 00:00:00 2001 From: whitequark Date: Sat, 18 Jul 2015 08:13:49 +0300 Subject: [PATCH 103/369] Fix inference for globals. --- artiq/compiler/asttyped.py | 2 +- artiq/compiler/transforms/asttyped_rewriter.py | 4 ++-- lit-test/compiler/inferencer/scoping.py | 7 ++++--- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/artiq/compiler/asttyped.py b/artiq/compiler/asttyped.py index 7d38bd823..e24003aba 100644 --- a/artiq/compiler/asttyped.py +++ b/artiq/compiler/asttyped.py @@ -18,7 +18,7 @@ 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) - list of variables resolved as globals + set of variables resolved as globals """ # Typed versions of untyped nodes diff --git a/artiq/compiler/transforms/asttyped_rewriter.py b/artiq/compiler/transforms/asttyped_rewriter.py index 244f99fd0..d05a73cd1 100644 --- a/artiq/compiler/transforms/asttyped_rewriter.py +++ b/artiq/compiler/transforms/asttyped_rewriter.py @@ -151,8 +151,8 @@ class LocalExtractor(algorithm.Visitor): continue self.global_.add(name) - self._assignable(name) - self.env_stack[1][name] = self.typing_env[name] + if name in self.env_stack[1]: + self.typing_env[name] = self.env_stack[1][name] def visit_Nonlocal(self, node): for name, loc in zip(node.names, node.name_locs): diff --git a/lit-test/compiler/inferencer/scoping.py b/lit-test/compiler/inferencer/scoping.py index 595daecf6..604b2425d 100644 --- a/lit-test/compiler/inferencer/scoping.py +++ b/lit-test/compiler/inferencer/scoping.py @@ -1,8 +1,9 @@ # RUN: %python -m artiq.compiler.testbench.inferencer %s >%t # RUN: OutputCheck %s --file-to-check=%t +# CHECK-L: []:list(elt=int(width='a)) +x = [] + def f(): global x - x = 1 -# CHECK-L: [x:int(width='a)] -[x] + x[0] = 1 From 5ad02d52821213e21afa71685b149a721e1e4c51 Mon Sep 17 00:00:00 2001 From: whitequark Date: Sat, 18 Jul 2015 09:10:41 +0300 Subject: [PATCH 104/369] Fix ARTIQ IR generation for variables declared global. --- artiq/compiler/ir.py | 10 ++++- .../compiler/transforms/asttyped_rewriter.py | 6 ++- artiq/compiler/transforms/ir_generator.py | 40 +++++++++++++++---- 3 files changed, 45 insertions(+), 11 deletions(-) diff --git a/artiq/compiler/ir.py b/artiq/compiler/ir.py index b7977cc10..86af6062c 100644 --- a/artiq/compiler/ir.py +++ b/artiq/compiler/ir.py @@ -426,6 +426,12 @@ class TEnvironment(types.TMono): 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. @@ -487,7 +493,7 @@ class GetLocal(Instruction): :param var_name: (string) local variable name """ def __init__(self, env, var_name, name=""): - assert isinstance(env, Alloc) + assert isinstance(env, Value) assert isinstance(env.type, TEnvironment) assert isinstance(var_name, str) super().__init__([env], env.type.type_of(var_name), name) @@ -513,7 +519,7 @@ class SetLocal(Instruction): :param value: (:class:`Value`) value to assign """ def __init__(self, env, var_name, value, name=""): - assert isinstance(env, Alloc) + assert isinstance(env, Value) assert isinstance(env.type, TEnvironment) assert isinstance(var_name, str) assert env.type.type_of(var_name) == value.type diff --git a/artiq/compiler/transforms/asttyped_rewriter.py b/artiq/compiler/transforms/asttyped_rewriter.py index d05a73cd1..db4e7f244 100644 --- a/artiq/compiler/transforms/asttyped_rewriter.py +++ b/artiq/compiler/transforms/asttyped_rewriter.py @@ -151,7 +151,11 @@ class LocalExtractor(algorithm.Visitor): continue self.global_.add(name) - if name in self.env_stack[1]: + 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): diff --git a/artiq/compiler/transforms/ir_generator.py b/artiq/compiler/transforms/ir_generator.py index ddb638c02..834aed7ff 100644 --- a/artiq/compiler/transforms/ir_generator.py +++ b/artiq/compiler/transforms/ir_generator.py @@ -35,6 +35,8 @@ class IRGenerator(algorithm.Visitor): 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.Environment`) @@ -65,6 +67,7 @@ class IRGenerator(algorithm.Visitor): self.name = [module_name] self.current_loc = None self.current_function = None + self.current_globals = set() self.current_block = None self.current_env = None self.current_private_env = None @@ -179,7 +182,13 @@ class IRGenerator(algorithm.Visitor): entry = self.add_block() old_block, self.current_block = self.current_block, entry - env_type = ir.TEnvironment(node.typing_env, self.current_env.type) + 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(env_without_globals, self.current_env.type) env = self.append(ir.Alloc([], env_type, name="env")) old_env, self.current_env = self.current_env, env @@ -201,22 +210,25 @@ class IRGenerator(algorithm.Visitor): if is_lambda: self.terminate(ir.Return(result)) elif builtins.is_none(typ.ret): - self.terminate(ir.Return(ir.Constant(None, builtins.TNone()))) + if not self.current_block.is_terminated(): + self.current_block.append(ir.Return(ir.Constant(None, builtins.TNone()))) else: - self.terminate(ir.Unreachable()) + if not self.current_block.is_terminated(): + self.current_block.append(ir.Unreachable()) return func finally: self.name = old_name 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 def visit_FunctionDefT(self, node): func = self.visit_function(node, is_lambda=False) - self.append(ir.SetLocal(self.current_env, node.name, func)) + self._set_local(node.name, func) def visit_Return(self, node): if node.value is None: @@ -465,7 +477,7 @@ class IRGenerator(algorithm.Visitor): if handler_node.name is not None: exn = self.append(ir.Builtin("exncast", [landingpad], handler_node.name_type)) - self.append(ir.SetLocal(self.current_env, handler_node.name, exn)) + self._set_local(handler_node.name, exn) self.visit(handler_node.body) if any(node.finalbody): @@ -545,12 +557,24 @@ class IRGenerator(algorithm.Visitor): 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): + return self.append(ir.GetLocal(self._env_for(name), name, name="local." + name)) + + def _set_local(self, name, value): + self.append(ir.SetLocal(self._env_for(name), name, value)) + def visit_NameT(self, node): if self.current_assign is None: - return self.append(ir.GetLocal(self.current_env, node.id, - name="{}.{}".format(self.current_env.name, node.id))) + return self._get_local(node.id) else: - self.append(ir.SetLocal(self.current_env, node.id, self.current_assign)) + return self._set_local(node.id, self.current_assign) def visit_AttributeT(self, node): try: From 8e1cc8d98557955a23a9d90de555d4ca1dd8ef46 Mon Sep 17 00:00:00 2001 From: whitequark Date: Sat, 18 Jul 2015 09:27:34 +0300 Subject: [PATCH 105/369] Add an explicit ARTIQ IR instruction to create a closure. --- artiq/compiler/ir.py | 34 +++++++++++++++++------ artiq/compiler/transforms/ir_generator.py | 10 ++++--- 2 files changed, 32 insertions(+), 12 deletions(-) diff --git a/artiq/compiler/ir.py b/artiq/compiler/ir.py index 86af6062c..cc8fda5cc 100644 --- a/artiq/compiler/ir.py +++ b/artiq/compiler/ir.py @@ -344,14 +344,13 @@ class Argument(NamedValue): def __str__(self): return self.as_operand() -class Function(Value): +class Function: """ A function containing SSA IR. """ def __init__(self, typ, name, arguments): - super().__init__(typ) - self.name = name + self.type, self.name = typ, name self.names, self.arguments, self.basic_blocks = set(), [], [] self.set_arguments(arguments) @@ -389,10 +388,6 @@ class Function(Value): return [block for block in self.basic_blocks if block.is_terminated() and successor in block.successors()] - def as_operand(self): - return "{} @{}".format(types.TypePrinter().name(self.type), - escape_name(self.name)) - def __str__(self): printer = types.TypePrinter() lines = [] @@ -766,6 +761,30 @@ class Builtin(Instruction): 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 opcode(self): + return "closure({})".format(self.target_function.name) + + def get_environment(self): + return self.operands[0] + class Call(Instruction): """ A function call operation. @@ -776,7 +795,6 @@ class Call(Instruction): :param args: (list of :class:`Value`) function arguments """ def __init__(self, func, args, name=""): - print(func) assert isinstance(func, Value) for arg in args: assert isinstance(arg, Value) super().__init__([func] + args, func.type.ret, name) diff --git a/artiq/compiler/transforms/ir_generator.py b/artiq/compiler/transforms/ir_generator.py index 834aed7ff..3edb90160 100644 --- a/artiq/compiler/transforms/ir_generator.py +++ b/artiq/compiler/transforms/ir_generator.py @@ -131,7 +131,8 @@ class IRGenerator(algorithm.Visitor): env = self.append(ir.Alloc([], ir.TEnvironment(node.typing_env), name="env")) old_env, self.current_env = self.current_env, env - priv_env = self.append(ir.Alloc([], ir.TEnvironment({ ".return": typ.ret }))) + priv_env = self.append(ir.Alloc([], ir.TEnvironment({ ".return": typ.ret }), + name="privenv")) old_priv_env, self.current_private_env = self.current_private_env, priv_env self.generic_visit(node) @@ -193,7 +194,8 @@ class IRGenerator(algorithm.Visitor): old_env, self.current_env = self.current_env, env if not is_lambda: - priv_env = self.append(ir.Alloc([], ir.TEnvironment({ ".return": typ.ret }))) + priv_env = self.append(ir.Alloc([], ir.TEnvironment({ ".return": typ.ret }), + name="privenv")) old_priv_env, self.current_private_env = self.current_private_env, priv_env self.append(ir.SetLocal(env, ".outer", env_arg)) @@ -215,8 +217,6 @@ class IRGenerator(algorithm.Visitor): else: if not self.current_block.is_terminated(): self.current_block.append(ir.Unreachable()) - - return func finally: self.name = old_name self.current_function = old_func @@ -226,6 +226,8 @@ class IRGenerator(algorithm.Visitor): 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_lambda=False) self._set_local(node.name, func) From 47cbadb564591b8dae2daaee8ea93d0994d44844 Mon Sep 17 00:00:00 2001 From: whitequark Date: Sat, 18 Jul 2015 09:41:53 +0300 Subject: [PATCH 106/369] Revert "Ensure bindings are created in correct order for e.g. "x, y = y, x"." This reverts commit bcd18322035d7f9272e1ed1d0050fede6aa0c665. The bindings are actually created in lexical order, as evident in e.g. "x = lambda: x". The safety provided by this check should be instead provided by a local access analysis. --- artiq/compiler/transforms/asttyped_rewriter.py | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/artiq/compiler/transforms/asttyped_rewriter.py b/artiq/compiler/transforms/asttyped_rewriter.py index db4e7f244..0c353bee6 100644 --- a/artiq/compiler/transforms/asttyped_rewriter.py +++ b/artiq/compiler/transforms/asttyped_rewriter.py @@ -108,25 +108,12 @@ class LocalExtractor(algorithm.Visitor): def visit_Name(self, node): if self.in_assign: - # Code like: + # code like: # x = 1 # def f(): # x = 1 # creates a new binding for x in f's scope self._assignable(node.id) - else: - # This is duplicated here as well as below so that - # code like: - # x, y = y, x - # where y and x were not defined earlier would be invalid. - if node.id in self.typing_env: - return - for outer_env in reversed(self.env_stack): - if node.id in outer_env: - return - diag = diagnostic.Diagnostic("fatal", - "name '{name}' is not bound to anything", {"name":node.id}, node.loc) - self.engine.process(diag) def visit_Attribute(self, node): self.visit_in_assign(node.value, in_assign=False) From 224a93fde32b0f66b4a538a3d95cd64dc87054ca Mon Sep 17 00:00:00 2001 From: whitequark Date: Sat, 18 Jul 2015 20:48:11 +0300 Subject: [PATCH 107/369] Make compiler.ir.BasicBlock.predecessors much faster. --- artiq/compiler/ir.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/artiq/compiler/ir.py b/artiq/compiler/ir.py index cc8fda5cc..3b77db9b7 100644 --- a/artiq/compiler/ir.py +++ b/artiq/compiler/ir.py @@ -303,8 +303,7 @@ class BasicBlock(NamedValue): return self.terminator().successors() def predecessors(self): - assert self.function is not None - return self.function.predecessors_of(self) + return [use.basic_block for use in self.uses if isinstance(use, Terminator)] def __str__(self): # Header @@ -384,9 +383,6 @@ class Function: basic_block._detach() self.basic_blocks.remove(basic_block) - def predecessors_of(self, successor): - return [block for block in self.basic_blocks - if block.is_terminated() and successor in block.successors()] def __str__(self): printer = types.TypePrinter() From 603d49dffa99dfa1e86fd5bc03894fb885b8373d Mon Sep 17 00:00:00 2001 From: whitequark Date: Sat, 18 Jul 2015 20:48:52 +0300 Subject: [PATCH 108/369] Add a dominator analysis. --- artiq/compiler/analyses/__init__.py | 1 + artiq/compiler/analyses/domination.py | 48 +++++++++++++++++++++++++++ artiq/compiler/ir.py | 10 ++++++ 3 files changed, 59 insertions(+) create mode 100644 artiq/compiler/analyses/__init__.py create mode 100644 artiq/compiler/analyses/domination.py diff --git a/artiq/compiler/analyses/__init__.py b/artiq/compiler/analyses/__init__.py new file mode 100644 index 000000000..f7f69f651 --- /dev/null +++ b/artiq/compiler/analyses/__init__.py @@ -0,0 +1 @@ +from .domination import DominatorTree diff --git a/artiq/compiler/analyses/domination.py b/artiq/compiler/analyses/domination.py new file mode 100644 index 000000000..8963cf189 --- /dev/null +++ b/artiq/compiler/analyses/domination.py @@ -0,0 +1,48 @@ +""" +:class:`DominatorTree` computes the dominance relation over +control flow graphs. + +See http://www.cs.colostate.edu/~mstrout/CS553/slides/lecture04.pdf. +""" + +from functools import reduce, cmp_to_key + +# Key Idea +# If a node dominates all +# predecessors of node n, then it +# also dominates node n +class DominatorTree: + def __init__(self, func): + entry = func.get_entry() + + self.dominated_by = { entry: {entry} } + for block in func.basic_blocks: + if block != entry: + self.dominated_by[block] = set(func.basic_blocks) + + predecessors = {block: block.predecessors() for block in func.basic_blocks} + while True: + changed = False + + for block in func.basic_blocks: + if block == entry: + continue + + if not any(predecessors[block]): + # Unreachable blocks are dominated by everything + continue + + new_dominated_by = {block}.union( + reduce(lambda a, b: a.intersection(b), + (self.dominated_by[pred] for pred in predecessors[block]))) + if new_dominated_by != self.dominated_by[block]: + self.dominated_by[block] = new_dominated_by + changed = True + + if not changed: + break + + def in_domination_order(self): + blocks = list(self.dominated_by.keys()) + blocks.sort(key=cmp_to_key(lambda a, b: a in self.dominated_by[b])) + return blocks diff --git a/artiq/compiler/ir.py b/artiq/compiler/ir.py index 3b77db9b7..ad36fbbeb 100644 --- a/artiq/compiler/ir.py +++ b/artiq/compiler/ir.py @@ -335,6 +335,9 @@ class BasicBlock(NamedValue): return "\n".join(lines) + def __repr__(self): + return "".format(self.name) + class Argument(NamedValue): """ A function argument. @@ -383,6 +386,13 @@ class Function: basic_block._detach() self.basic_blocks.remove(basic_block) + def get_entry(self): + assert any(self.basic_blocks) + return self.basic_blocks[0] + + def instructions(self): + for basic_block in self.basic_blocks: + yield from iter(basic_block.instructions) def __str__(self): printer = types.TypePrinter() From f212ec0263b2b7f7822ae56c2f9f7d111f0bc196 Mon Sep 17 00:00:00 2001 From: whitequark Date: Sun, 19 Jul 2015 10:29:14 +0300 Subject: [PATCH 109/369] Add a trivial dead code elimination transform. Its purpose is to sweep up basic blocks with no predecessors, which are annoying to handle explicitly elsewhere. --- artiq/compiler/analyses/domination.py | 11 +---------- artiq/compiler/ir.py | 7 ++++++- artiq/compiler/transforms/__init__.py | 1 + .../transforms/dead_code_eliminator.py | 19 +++++++++++++++++++ 4 files changed, 27 insertions(+), 11 deletions(-) create mode 100644 artiq/compiler/transforms/dead_code_eliminator.py diff --git a/artiq/compiler/analyses/domination.py b/artiq/compiler/analyses/domination.py index 8963cf189..7fbd4d231 100644 --- a/artiq/compiler/analyses/domination.py +++ b/artiq/compiler/analyses/domination.py @@ -13,7 +13,7 @@ from functools import reduce, cmp_to_key # also dominates node n class DominatorTree: def __init__(self, func): - entry = func.get_entry() + entry = func.entry() self.dominated_by = { entry: {entry} } for block in func.basic_blocks: @@ -28,10 +28,6 @@ class DominatorTree: if block == entry: continue - if not any(predecessors[block]): - # Unreachable blocks are dominated by everything - continue - new_dominated_by = {block}.union( reduce(lambda a, b: a.intersection(b), (self.dominated_by[pred] for pred in predecessors[block]))) @@ -41,8 +37,3 @@ class DominatorTree: if not changed: break - - def in_domination_order(self): - blocks = list(self.dominated_by.keys()) - blocks.sort(key=cmp_to_key(lambda a, b: a in self.dominated_by[b])) - return blocks diff --git a/artiq/compiler/ir.py b/artiq/compiler/ir.py index ad36fbbeb..1aaf0d97f 100644 --- a/artiq/compiler/ir.py +++ b/artiq/compiler/ir.py @@ -261,6 +261,11 @@ class BasicBlock(NamedValue): if self.function is not None: self.function.remove(self) + def erase(self): + for insn in self.instructions: + insn.erase() + self.remove_from_parent() + def prepend(self, insn): assert isinstance(insn, Instruction) insn.set_basic_block(self) @@ -386,7 +391,7 @@ class Function: basic_block._detach() self.basic_blocks.remove(basic_block) - def get_entry(self): + def entry(self): assert any(self.basic_blocks) return self.basic_blocks[0] diff --git a/artiq/compiler/transforms/__init__.py b/artiq/compiler/transforms/__init__.py index 4f059a686..64dca0906 100644 --- a/artiq/compiler/transforms/__init__.py +++ b/artiq/compiler/transforms/__init__.py @@ -2,3 +2,4 @@ from .asttyped_rewriter import ASTTypedRewriter from .inferencer import Inferencer from .int_monomorphizer import IntMonomorphizer from .ir_generator import IRGenerator +from .dead_code_eliminator import DeadCodeEliminator diff --git a/artiq/compiler/transforms/dead_code_eliminator.py b/artiq/compiler/transforms/dead_code_eliminator.py new file mode 100644 index 000000000..92d82b4a7 --- /dev/null +++ b/artiq/compiler/transforms/dead_code_eliminator.py @@ -0,0 +1,19 @@ +""" +:class:`DeadCodeEliminator` is a very simple dead code elimination +transform: it only removes basic blocks with no predecessors. +""" + +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): + for block in func.basic_blocks: + if not any(block.predecessors()) and block != func.entry(): + block.erase() From 8eedb3bc444f53f5f488f5a2f1c4b1e9aeddd5ea Mon Sep 17 00:00:00 2001 From: whitequark Date: Sun, 19 Jul 2015 10:29:33 +0300 Subject: [PATCH 110/369] Fix IRGenerator.append(loc=...). --- artiq/compiler/transforms/ir_generator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/artiq/compiler/transforms/ir_generator.py b/artiq/compiler/transforms/ir_generator.py index 3edb90160..e19bc06f4 100644 --- a/artiq/compiler/transforms/ir_generator.py +++ b/artiq/compiler/transforms/ir_generator.py @@ -89,7 +89,7 @@ class IRGenerator(algorithm.Visitor): block = self.current_block if insn.loc is None: - insn.loc = self.current_loc + insn.loc = loc return block.append(insn) def terminate(self, insn): From 4bd83fb43de60fee49dfaa6ccfe10692481eac5a Mon Sep 17 00:00:00 2001 From: whitequark Date: Sun, 19 Jul 2015 10:30:42 +0300 Subject: [PATCH 111/369] Use ".k" instead of "k" for the finalizer continuation variable. The dot signifies that this is an internal variable and it does not need to be tracked as if it was a user-defined one. --- artiq/compiler/transforms/ir_generator.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/artiq/compiler/transforms/ir_generator.py b/artiq/compiler/transforms/ir_generator.py index e19bc06f4..159dd892d 100644 --- a/artiq/compiler/transforms/ir_generator.py +++ b/artiq/compiler/transforms/ir_generator.py @@ -424,30 +424,30 @@ class IRGenerator(algorithm.Visitor): if any(node.finalbody): # k for continuation - final_state = self.append(ir.Alloc([], ir.TEnvironment({ "k": ir.TBasicBlock() }))) + final_state = self.append(ir.Alloc([], ir.TEnvironment({ ".k": ir.TBasicBlock() }))) final_targets = [] if self.break_target is not None: break_proxy = self.add_block("try.break") old_break, self.break_target = self.break_target, break_proxy - break_proxy.append(ir.SetLocal(final_state, "k", old_break)) + break_proxy.append(ir.SetLocal(final_state, ".k", old_break)) final_targets.append(old_break) if self.continue_target is not None: continue_proxy = self.add_block("try.continue") old_continue, self.continue_target = self.continue_target, continue_proxy - continue_proxy.append(ir.SetLocal(final_state, "k", old_continue)) + continue_proxy.append(ir.SetLocal(final_state, ".k", old_continue)) final_targets.append(old_continue) return_proxy = self.add_block("try.return") old_return, self.return_target = self.return_target, return_proxy if old_return is not None: - return_proxy.append(ir.SetLocal(final_state, "k", old_return)) + return_proxy.append(ir.SetLocal(final_state, ".k", old_return)) final_targets.append(old_return) 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)) - return_proxy.append(ir.SetLocal(final_state, "k", return_action)) + return_proxy.append(ir.SetLocal(final_state, ".k", return_action)) final_targets.append(return_action) body = self.add_block("try.body") @@ -489,7 +489,7 @@ class IRGenerator(algorithm.Visitor): self.visit(node.finalbody) if not self.current_block.is_terminated(): - dest = self.append(ir.GetLocal(final_state, "k")) + dest = self.append(ir.GetLocal(final_state, ".k")) self.append(ir.IndirectBranch(dest, final_targets)) tail = self.add_block("try.tail") @@ -501,11 +501,11 @@ class IRGenerator(algorithm.Visitor): return_proxy.append(ir.Branch(finalizer)) if not body.is_terminated(): if any(node.finalbody): - body.append(ir.SetLocal(final_state, "k", tail)) + body.append(ir.SetLocal(final_state, ".k", tail)) body.append(ir.Branch(finalizer)) for handler in handlers: if not handler.is_terminated(): - handler.append(ir.SetLocal(final_state, "k", tail)) + handler.append(ir.SetLocal(final_state, ".k", tail)) handler.append(ir.Branch(tail)) else: body.append(ir.Branch(tail)) From adf18bb04263e1e42e3b67911dc5e2b8dd64543f Mon Sep 17 00:00:00 2001 From: whitequark Date: Sun, 19 Jul 2015 10:31:11 +0300 Subject: [PATCH 112/369] Fix assignment to tuples in IRGenerator. --- artiq/compiler/transforms/ir_generator.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/artiq/compiler/transforms/ir_generator.py b/artiq/compiler/transforms/ir_generator.py index 159dd892d..000e016cf 100644 --- a/artiq/compiler/transforms/ir_generator.py +++ b/artiq/compiler/transforms/ir_generator.py @@ -721,22 +721,22 @@ class IRGenerator(algorithm.Visitor): else: try: old_assign = self.current_assign - for index, elt in enumerate(node.elts): + for index, elt_node in enumerate(node.elts): self.current_assign = \ self.append(ir.GetAttr(old_assign, index, - name="{}.{}".format(old_assign.name, - _readable_name(index)))) - self.visit(elt) + name="{}.{}".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) for elt in node.elts] + 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 in enumerate(elts): - self.append(ir.SetElem(lst, ir.Constant(index, self._size_type), elt)) + 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.append(ir.Builtin("len", [self.current_assign], self._size_type)) From f5d9e11b38467a66608c2e16526416fc831d7e8e Mon Sep 17 00:00:00 2001 From: whitequark Date: Sun, 19 Jul 2015 11:30:53 +0300 Subject: [PATCH 113/369] Remove irgen tests. These are too hard to write and will be replaced by integration tests of ARTIQ IR generator + LLVM IR generator once the latter gets implemented. --- lit-test/compiler/irgen/empty.py | 7 ------- lit-test/compiler/irgen/eval.py | 9 --------- lit-test/compiler/irgen/if.py | 21 --------------------- lit-test/compiler/irgen/while.py | 25 ------------------------- 4 files changed, 62 deletions(-) delete mode 100644 lit-test/compiler/irgen/empty.py delete mode 100644 lit-test/compiler/irgen/eval.py delete mode 100644 lit-test/compiler/irgen/if.py delete mode 100644 lit-test/compiler/irgen/while.py diff --git a/lit-test/compiler/irgen/empty.py b/lit-test/compiler/irgen/empty.py deleted file mode 100644 index 85beee74d..000000000 --- a/lit-test/compiler/irgen/empty.py +++ /dev/null @@ -1,7 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.irgen %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -# CHECK-L: NoneType input.__modinit__() { -# CHECK-L: 1: -# CHECK-L: return NoneType None -# CHECK-L: } diff --git a/lit-test/compiler/irgen/eval.py b/lit-test/compiler/irgen/eval.py deleted file mode 100644 index cd88b9b1e..000000000 --- a/lit-test/compiler/irgen/eval.py +++ /dev/null @@ -1,9 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.irgen %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -2 + 2 -# CHECK-L: NoneType input.__modinit__() { -# CHECK-L: 1: -# CHECK-L: %2 = int(width=32) eval `2 + 2` -# CHECK-L: return NoneType None -# CHECK-L: } diff --git a/lit-test/compiler/irgen/if.py b/lit-test/compiler/irgen/if.py deleted file mode 100644 index 28d25d944..000000000 --- a/lit-test/compiler/irgen/if.py +++ /dev/null @@ -1,21 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.irgen %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -if 1: - 2 -else: - 3 - -# CHECK-L: NoneType input.__modinit__() { -# CHECK-L: 1: -# CHECK-L: %2 = int(width=32) eval `1` -# CHECK-L: branch_if int(width=32) %2, ssa.basic_block %3, ssa.basic_block %5 -# CHECK-L: 3: -# CHECK-L: %4 = int(width=32) eval `2` -# CHECK-L: branch ssa.basic_block %7 -# CHECK-L: 5: -# CHECK-L: %6 = int(width=32) eval `3` -# CHECK-L: branch ssa.basic_block %7 -# CHECK-L: 7: -# CHECK-L: return NoneType None -# CHECK-L: } diff --git a/lit-test/compiler/irgen/while.py b/lit-test/compiler/irgen/while.py deleted file mode 100644 index 6ec344be0..000000000 --- a/lit-test/compiler/irgen/while.py +++ /dev/null @@ -1,25 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.irgen %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -while 1: - 2 -else: - 3 -4 - -# CHECK-L: NoneType input.__modinit__() { -# CHECK-L: 1: -# CHECK-L: branch ssa.basic_block %2 -# CHECK-L: 2: -# CHECK-L: %9 = int(width=32) eval `1` -# CHECK-L: branch_if int(width=32) %9, ssa.basic_block %5, ssa.basic_block %7 -# CHECK-L: 4: -# CHECK-L: branch ssa.basic_block %7 -# CHECK-L: 5: -# CHECK-L: %6 = int(width=32) eval `2` -# CHECK-L: branch ssa.basic_block %7 -# CHECK-L: 7: -# CHECK-L: %8 = int(width=32) eval `3` -# CHECK-L: %13 = int(width=32) eval `4` -# CHECK-L: return NoneType None -# CHECK-L: } From ac491fae4776440f7323b6728515fb7f1a88bb6b Mon Sep 17 00:00:00 2001 From: whitequark Date: Sun, 19 Jul 2015 11:44:51 +0300 Subject: [PATCH 114/369] Add LocalAccessValidator. --- artiq/compiler/ir.py | 73 ++++++------- artiq/compiler/module.py | 27 ++--- artiq/compiler/transforms/ir_generator.py | 20 +++- artiq/compiler/validators/__init__.py | 1 + artiq/compiler/validators/local_access.py | 121 ++++++++++++++++++++++ lit-test/compiler/local_access/invalid.py | 20 ++++ lit-test/compiler/local_access/valid.py | 7 ++ 7 files changed, 216 insertions(+), 53 deletions(-) create mode 100644 artiq/compiler/validators/local_access.py create mode 100644 lit-test/compiler/local_access/invalid.py create mode 100644 lit-test/compiler/local_access/valid.py diff --git a/artiq/compiler/ir.py b/artiq/compiler/ir.py index 1aaf0d97f..58e3566eb 100644 --- a/artiq/compiler/ir.py +++ b/artiq/compiler/ir.py @@ -395,6 +395,9 @@ class Function: 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) @@ -508,7 +511,7 @@ class GetLocal(Instruction): def opcode(self): return "getlocal({})".format(self.var_name) - def get_env(self): + def environment(self): return self.operands[0] class SetLocal(Instruction): @@ -536,10 +539,10 @@ class SetLocal(Instruction): def opcode(self): return "setlocal({})".format(self.var_name) - def get_env(self): + def environment(self): return self.operands[0] - def get_value(self): + def value(self): return self.operands[1] class GetAttr(Instruction): @@ -568,7 +571,7 @@ class GetAttr(Instruction): def opcode(self): return "getattr({})".format(repr(self.attr)) - def get_env(self): + def env(self): return self.operands[0] class SetAttr(Instruction): @@ -597,10 +600,10 @@ class SetAttr(Instruction): def opcode(self): return "setattr({})".format(repr(self.attr)) - def get_env(self): + def env(self): return self.operands[0] - def get_value(self): + def value(self): return self.operands[1] class GetElem(Instruction): @@ -620,10 +623,10 @@ class GetElem(Instruction): def opcode(self): return "getelem" - def get_list(self): + def list(self): return self.operands[0] - def get_index(self): + def index(self): return self.operands[1] class SetElem(Instruction): @@ -646,13 +649,13 @@ class SetElem(Instruction): def opcode(self): return "setelem" - def get_list(self): + def list(self): return self.operands[0] - def get_index(self): + def index(self): return self.operands[1] - def get_value(self): + def value(self): return self.operands[2] class UnaryOp(Instruction): @@ -675,7 +678,7 @@ class UnaryOp(Instruction): def opcode(self): return "unaryop({})".format(type(self.op).__name__) - def get_operand(self): + def operand(self): return self.operands[0] class Coerce(Instruction): @@ -691,7 +694,7 @@ class Coerce(Instruction): def opcode(self): return "coerce" - def get_value(self): + def value(self): return self.operands[0] class BinaryOp(Instruction): @@ -717,10 +720,10 @@ class BinaryOp(Instruction): def opcode(self): return "binaryop({})".format(type(self.op).__name__) - def get_lhs(self): + def lhs(self): return self.operands[0] - def get_rhs(self): + def rhs(self): return self.operands[1] class Compare(Instruction): @@ -746,10 +749,10 @@ class Compare(Instruction): def opcode(self): return "compare({})".format(type(self.op).__name__) - def get_lhs(self): + def lhs(self): return self.operands[0] - def get_rhs(self): + def rhs(self): return self.operands[1] class Builtin(Instruction): @@ -793,7 +796,7 @@ class Closure(Instruction): def opcode(self): return "closure({})".format(self.target_function.name) - def get_environment(self): + def environment(self): return self.operands[0] class Call(Instruction): @@ -813,10 +816,10 @@ class Call(Instruction): def opcode(self): return "call" - def get_function(self): + def function(self): return self.operands[0] - def get_arguments(self): + def arguments(self): return self.operands[1:] class Select(Instruction): @@ -839,13 +842,13 @@ class Select(Instruction): def opcode(self): return "select" - def get_condition(self): + def condition(self): return self.operands[0] - def get_if_true(self): + def if_true(self): return self.operands[1] - def get_if_false(self): + def if_false(self): return self.operands[2] class Branch(Terminator): @@ -863,7 +866,7 @@ class Branch(Terminator): def opcode(self): return "branch" - def get_target(self): + def target(self): return self.operands[0] class BranchIf(Terminator): @@ -885,13 +888,13 @@ class BranchIf(Terminator): def opcode(self): return "branchif" - def get_condition(self): + def condition(self): return self.operands[0] - def get_if_true(self): + def if_true(self): return self.operands[1] - def get_if_false(self): + def if_false(self): return self.operands[2] class IndirectBranch(Terminator): @@ -911,10 +914,10 @@ class IndirectBranch(Terminator): def opcode(self): return "indirectbranch" - def get_target(self): + def target(self): return self.operands[0] - def get_destinations(self): + def destinations(self): return self.operands[1:] def add_destination(self, destination): @@ -939,7 +942,7 @@ class Return(Terminator): def opcode(self): return "return" - def get_value(self): + def value(self): return self.operands[0] class Unreachable(Terminator): @@ -971,7 +974,7 @@ class Raise(Terminator): def opcode(self): return "raise" - def get_value(self): + def value(self): return self.operands[0] class Invoke(Terminator): @@ -995,16 +998,16 @@ class Invoke(Terminator): def opcode(self): return "invoke" - def get_function(self): + def function(self): return self.operands[0] - def get_arguments(self): + def arguments(self): return self.operands[1:-2] - def get_normal_target(self): + def normal_target(self): return self.operands[-2] - def get_exception_target(self): + def exception_target(self): return self.operands[-1] def _operands_as_string(self): diff --git a/artiq/compiler/module.py b/artiq/compiler/module.py index 833896d79..22ae58577 100644 --- a/artiq/compiler/module.py +++ b/artiq/compiler/module.py @@ -11,27 +11,28 @@ class Module: if engine is None: engine = diagnostic.Engine(all_errors_are_fatal=True) - module_name, _ = os.path.splitext(os.path.basename(source_buffer.name)) + self.name, _ = os.path.splitext(os.path.basename(source_buffer.name)) asttyped_rewriter = transforms.ASTTypedRewriter(engine=engine) inferencer = transforms.Inferencer(engine=engine) int_monomorphizer = transforms.IntMonomorphizer(engine=engine) monomorphism_validator = validators.MonomorphismValidator(engine=engine) escape_validator = validators.EscapeValidator(engine=engine) - ir_generator = transforms.IRGenerator(engine=engine, module_name=module_name) + ir_generator = transforms.IRGenerator(engine=engine, module_name=self.name) + dead_code_eliminator = transforms.DeadCodeEliminator(engine=engine) + local_access_validator = validators.LocalAccessValidator(engine=engine) - parsetree, comments = parse_buffer(source_buffer, engine=engine) - typedtree = asttyped_rewriter.visit(parsetree) - inferencer.visit(typedtree) - int_monomorphizer.visit(typedtree) - inferencer.visit(typedtree) - monomorphism_validator.visit(typedtree) - escape_validator.visit(typedtree) - ir_generator.visit(typedtree) - - self.name = module_name + self.parsetree, self.comments = parse_buffer(source_buffer, engine=engine) + self.typedtree = asttyped_rewriter.visit(self.parsetree) self.globals = asttyped_rewriter.globals - self.ir = ir_generator.functions + inferencer.visit(self.typedtree) + int_monomorphizer.visit(self.typedtree) + inferencer.visit(self.typedtree) + monomorphism_validator.visit(self.typedtree) + escape_validator.visit(self.typedtree) + self.ir = ir_generator.visit(self.typedtree) + dead_code_eliminator.process(self.ir) + local_access_validator.process(self.ir) @classmethod def from_string(cls, source_string, name="input.py", first_line=1, engine=None): diff --git a/artiq/compiler/transforms/ir_generator.py b/artiq/compiler/transforms/ir_generator.py index 000e016cf..c30624cd6 100644 --- a/artiq/compiler/transforms/ir_generator.py +++ b/artiq/compiler/transforms/ir_generator.py @@ -138,7 +138,7 @@ class IRGenerator(algorithm.Visitor): self.generic_visit(node) self.terminate(ir.Return(ir.Constant(None, builtins.TNone()))) - return func + return self.functions finally: self.current_function = old_func self.current_block = old_block @@ -245,8 +245,18 @@ class IRGenerator(algorithm.Visitor): self.append(ir.Branch(self.return_target)) def visit_Expr(self, node): - # ignore the value, do it for side effects - self.visit(node.value) + # 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.BinaryOp(ast.Add(loc=None), + ir.Constant(0, self._size_type), ir.Constant(0, self._size_type))) def visit_Assign(self, node): try: @@ -288,9 +298,9 @@ class IRGenerator(algorithm.Visitor): if any(node.orelse): if not if_false.is_terminated(): if_false.append(ir.Branch(tail)) - head.append(ir.BranchIf(cond, if_true, if_false)) + self.append(ir.BranchIf(cond, if_true, if_false), block=head) else: - head.append(ir.BranchIf(cond, if_true, tail)) + self.append(ir.BranchIf(cond, if_true, tail), block=head) def visit_While(self, node): try: diff --git a/artiq/compiler/validators/__init__.py b/artiq/compiler/validators/__init__.py index a90a89c69..7f0719ea9 100644 --- a/artiq/compiler/validators/__init__.py +++ b/artiq/compiler/validators/__init__.py @@ -1,2 +1,3 @@ from .monomorphism import MonomorphismValidator from .escape import EscapeValidator +from .local_access import LocalAccessValidator diff --git a/artiq/compiler/validators/local_access.py b/artiq/compiler/validators/local_access.py new file mode 100644 index 000000000..4f2b4ffd0 --- /dev/null +++ b/artiq/compiler/validators/local_access.py @@ -0,0 +1,121 @@ +""" +:class:`LocalAccessValidator` verifies that local variables +are not accessed before being used. +""" + +from functools import reduce +from pythonparser import diagnostic +from .. import ir, analyses + +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 allocated in this func. + environments = [] + for insn in func.instructions(): + if isinstance(insn, ir.Alloc) and ir.is_environment(insn.type): + environments.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. + dom = 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 dom.dominated_by[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(lambda a, b: merge_state(a[env], b[env]), + 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: + if isinstance(insn, (ir.SetLocal, ir.GetLocal)) and \ + "." not in insn.var_name: + env, var_name = insn.environment(), insn.var_name + assert env in block_state + assert var_name in block_state[env] + + if isinstance(insn, ir.SetLocal): + # We've just initialized it. + block_state[env][var_name] = True + else: # isinstance(insn, ir.GetLocal) + if not block_state[env][var_name]: + # Oops, accessing it uninitialized. Find out where + # the uninitialized state comes from. + pred_at_fault = None + for pred, pred_state in zip(forward_edge_preds, pred_states): + if not pred_state[env][var_name]: + pred_at_fault = pred + assert pred_at_fault is not None + + # Report the error. + self._uninitialized_access(insn, pred_at_fault) + + # Save the state. + state[block] = block_state + + return block_state + + for block in func.basic_blocks: + traverse(block) + + def _uninitialized_access(self, insn, pred_at_fault): + uninitialized_loc = None + for pred_insn in reversed(pred_at_fault.instructions): + if pred_insn.loc is not None: + uninitialized_loc = pred_insn.loc.begin() + break + assert uninitialized_loc is not None + + note = diagnostic.Diagnostic("note", + "variable is not initialized when control flows from this point", {}, + uninitialized_loc) + diag = diagnostic.Diagnostic("error", + "variable '{name}' is not always initialized at this point", + {"name": insn.var_name}, + insn.loc, notes=[note]) + self.engine.process(diag) diff --git a/lit-test/compiler/local_access/invalid.py b/lit-test/compiler/local_access/invalid.py new file mode 100644 index 000000000..94561d2d6 --- /dev/null +++ b/lit-test/compiler/local_access/invalid.py @@ -0,0 +1,20 @@ +# RUN: %python -m artiq.compiler.testbench.module +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/lit-test/compiler/local_access/valid.py b/lit-test/compiler/local_access/valid.py new file mode 100644 index 000000000..0e598ed39 --- /dev/null +++ b/lit-test/compiler/local_access/valid.py @@ -0,0 +1,7 @@ +# RUN: %python -m artiq.compiler.testbench.module %s >%t + +if False: + x = 1 +else: + x = 2 +-x From 2c010b10eec528d0d642b7e6a3c94551252ba6ce Mon Sep 17 00:00:00 2001 From: whitequark Date: Sun, 19 Jul 2015 11:51:53 +0300 Subject: [PATCH 115/369] Remove UnaryOp ARTIQ IR instruction; rename BinaryOp to Arith. Everything it can express can also be expressed via Arith. --- artiq/compiler/ir.py | 31 +------ artiq/compiler/transforms/ir_generator.py | 98 ++++++++++++----------- 2 files changed, 55 insertions(+), 74 deletions(-) diff --git a/artiq/compiler/ir.py b/artiq/compiler/ir.py index 58e3566eb..89e1f4b1a 100644 --- a/artiq/compiler/ir.py +++ b/artiq/compiler/ir.py @@ -658,29 +658,6 @@ class SetElem(Instruction): def value(self): return self.operands[2] -class UnaryOp(Instruction): - """ - An unary operation on numbers. - - :ivar op: (:class:`pythonparser.ast.unaryop`) operation - """ - - """ - :param op: (:class:`pythonparser.ast.unaryop`) operation - :param operand: (:class:`Value`) operand - """ - def __init__(self, op, operand, name=""): - assert isinstance(op, ast.unaryop) - assert isinstance(operand, Value) - super().__init__([operand], operand.type, name) - self.op = op - - def opcode(self): - return "unaryop({})".format(type(self.op).__name__) - - def operand(self): - return self.operands[0] - class Coerce(Instruction): """ A coercion operation for numbers. @@ -697,11 +674,11 @@ class Coerce(Instruction): def value(self): return self.operands[0] -class BinaryOp(Instruction): +class Arith(Instruction): """ - A binary operation on numbers. + An arithmetic operation on numbers. - :ivar op: (:class:`pythonparser.ast.unaryop`) operation + :ivar op: (:class:`pythonparser.ast.operator`) operation """ """ @@ -718,7 +695,7 @@ class BinaryOp(Instruction): self.op = op def opcode(self): - return "binaryop({})".format(type(self.op).__name__) + return "arith({})".format(type(self.op).__name__) def lhs(self): return self.operands[0] diff --git a/artiq/compiler/transforms/ir_generator.py b/artiq/compiler/transforms/ir_generator.py index c30624cd6..776a226d0 100644 --- a/artiq/compiler/transforms/ir_generator.py +++ b/artiq/compiler/transforms/ir_generator.py @@ -255,8 +255,8 @@ class IRGenerator(algorithm.Visitor): def visit_Pass(self, node): # Insert a dummy instruction so that analyses which extract # locations from CFG have something to use. - self.append(ir.BinaryOp(ast.Add(loc=None), - ir.Constant(0, self._size_type), ir.Constant(0, self._size_type))) + self.append(ir.Arith(ast.Add(loc=None), + ir.Constant(0, self._size_type), ir.Constant(0, self._size_type))) def visit_Assign(self, node): try: @@ -270,7 +270,7 @@ class IRGenerator(algorithm.Visitor): def visit_AugAssign(self, node): lhs = self.visit(target) rhs = self.visit(node.value) - value = self.append(ir.BinaryOp(node.op, lhs, rhs)) + value = self.append(ir.Arith(node.op, lhs, rhs)) try: self.current_assign = value self.visit(node.target) @@ -346,8 +346,8 @@ class IRGenerator(algorithm.Visitor): 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.BinaryOp(ast.Sub(loc=None), stop, start)) - return self.append(ir.BinaryOp(ast.FloorDiv(loc=None), spread, step)) + spread = self.append(ir.Arith(ast.Sub(loc=None), stop, start)) + return self.append(ir.Arith(ast.FloorDiv(loc=None), spread, step)) else: assert False @@ -358,8 +358,8 @@ class IRGenerator(algorithm.Visitor): elif builtins.is_range(value.type): start = self.append(ir.GetAttr(value, "start")) step = self.append(ir.GetAttr(value, "step")) - offset = self.append(ir.BinaryOp(ast.Mult(loc=None), step, index)) - return self.append(ir.BinaryOp(ast.Add(loc=None), start, offset)) + 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 @@ -382,8 +382,7 @@ class IRGenerator(algorithm.Visitor): old_continue, self.continue_target = self.continue_target, continue_block self.current_block = continue_block - updated_index = self.append(ir.BinaryOp(ast.Add(loc=None), phi, - ir.Constant(1, phi.type))) + updated_index = self.append(ir.Arith(ast.Add(loc=None), phi, ir.Constant(1, phi.type))) phi.add_incoming(updated_index, continue_block) self.append(ir.Branch(head)) @@ -604,7 +603,7 @@ class IRGenerator(algorithm.Visitor): def _map_index(self, length, index): lt_0 = self.append(ir.Compare(ast.Lt(loc=None), index, ir.Constant(0, index.type))) - from_end = self.append(ir.BinaryOp(ast.Add(loc=None), length, index)) + 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))) @@ -696,9 +695,9 @@ class IRGenerator(algorithm.Visitor): else: step = ir.Constant(1, node.slice.type) - unstepped_size = self.append(ir.BinaryOp(ast.Sub(loc=None), - mapped_max_index, mapped_min_index)) - slice_size = self.append(ir.BinaryOp(ast.FloorDiv(loc=None), unstepped_size, step)) + 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)) self._make_check(self.append(ir.Compare(ast.Eq(loc=None), slice_size, length)), lambda: self.append(ir.Alloc([], builtins.TValueError()))) @@ -709,8 +708,8 @@ class IRGenerator(algorithm.Visitor): other_value = self.current_assign def body_gen(other_index): - offset = self.append(ir.BinaryOp(ast.Mult(loc=None), step, other_index)) - index = self.append(ir.BinaryOp(ast.Add(loc=None), min_index, offset)) + offset = self.append(ir.Arith(ast.Mult(loc=None), step, other_index)) + index = self.append(ir.Arith(ast.Add(loc=None), min_index, offset)) if self.current_assign is None: elem = self._iterable_get(value, index) @@ -719,8 +718,8 @@ class IRGenerator(algorithm.Visitor): elem = self.append(ir.GetElem(self.current_assign, other_index)) self.append(ir.SetElem(value, index, elem)) - return self.append(ir.BinaryOp(ast.Add(loc=None), other_index, - ir.Constant(1, node.slice.type))) + return self.append(ir.Arith(ast.Add(loc=None), other_index, + ir.Constant(1, node.slice.type))) self._make_loop(ir.Constant(0, node.slice.type), lambda index: self.append(ir.Compare(ast.Lt(loc=None), index, slice_size)), body_gen) @@ -790,8 +789,8 @@ class IRGenerator(algorithm.Visitor): mapped_elt = self.visit(node.elt) self.append(ir.SetElem(result, index, mapped_elt)) - return self.append(ir.BinaryOp(ast.Add(loc=None), index, - ir.Constant(1, length.type))) + 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) @@ -822,8 +821,15 @@ class IRGenerator(algorithm.Visitor): return self.append(ir.Select(node.operand, ir.Constant(False, builtins.TBool()), ir.Constant(True, builtins.TBool()))) - else: # Numeric operators - return self.append(ir.UnaryOp(node.op, self.visit(node.operand))) + elif isinstance(node.op, ast.USub): + operand = self.visit(node.operand) + return self.append(ir.Arith(ast.Sub(loc=None), + ir.Constant(0, operand.type), 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) @@ -836,9 +842,7 @@ class IRGenerator(algorithm.Visitor): def visit_BinOpT(self, node): if builtins.is_numeric(node.type): - return self.append(ir.BinaryOp(node.op, - self.visit(node.left), - self.visit(node.right))) + 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): @@ -852,15 +856,15 @@ class IRGenerator(algorithm.Visitor): lhs_length = self.append(ir.Builtin("len", [lhs], self._size_type)) rhs_length = self.append(ir.Builtin("len", [rhs], self._size_type)) - result_length = self.append(ir.BinaryOp(ast.Add(loc=None), lhs_length, rhs_length)) + 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.BinaryOp(ast.Add(loc=None), index, - ir.Constant(1, self._size_type))) + 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) @@ -868,10 +872,10 @@ class IRGenerator(algorithm.Visitor): # Copy rhs def body_gen(index): elt = self.append(ir.GetElem(rhs, index)) - result_index = self.append(ir.BinaryOp(ast.Add(loc=None), index, lhs_length)) + 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.BinaryOp(ast.Add(loc=None), index, - ir.Constant(1, self._size_type))) + 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) @@ -890,7 +894,7 @@ class IRGenerator(algorithm.Visitor): lst_length = self.append(ir.Builtin("len", [lst], self._size_type)) - result_length = self.append(ir.BinaryOp(ast.Mult(loc=None), lst_length, num)) + result_length = self.append(ir.Arith(ast.Mult(loc=None), lst_length, num)) result = self.append(ir.Alloc([result_length], node.type)) # num times... @@ -898,19 +902,19 @@ class IRGenerator(algorithm.Visitor): # ... copy the list def body_gen(lst_index): elt = self.append(ir.GetElem(lst, lst_index)) - base_index = self.append(ir.BinaryOp(ast.Mult(loc=None), - num_index, lst_length)) - result_index = self.append(ir.BinaryOp(ast.Add(loc=None), - base_index, 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.BinaryOp(ast.Add(loc=None), lst_index, - ir.Constant(1, self._size_type))) + 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.BinaryOp(ast.Add(loc=None), lst_length, - ir.Constant(1, self._size_type))) + return self.append(ir.Arith(ast.Add(loc=None), lst_length, + 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) @@ -955,8 +959,8 @@ class IRGenerator(algorithm.Visitor): loop_body2 = self.add_block() self.current_block = loop_body2 - index_next = self.append(ir.BinaryOp(ast.Add(loc=None), index_phi, - ir.Constant(1, self._size_type))) + 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) @@ -988,8 +992,8 @@ class IRGenerator(algorithm.Visitor): 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.BinaryOp(ast.Sub(loc=None), needle, start)) - mod_step = self.append(ir.BinaryOp(ast.Mod(loc=None), from_start, step)) + 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, @@ -1008,8 +1012,8 @@ class IRGenerator(algorithm.Visitor): loop_body2 = self.add_block() self.current_block = loop_body2 - return self.append(ir.BinaryOp(ast.Add(loc=None), index, - ir.Constant(1, length.type))) + 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)), @@ -1117,8 +1121,8 @@ class IRGenerator(algorithm.Visitor): def body_gen(index): elt = self._iterable_get(arg, index) self.append(ir.SetElem(result, index, elt)) - return self.append(ir.BinaryOp(ast.Add(loc=None), index, - ir.Constant(1, length.type))) + 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) From 7e3f91c0bba8dbd25ce44d7aa749337f460eea18 Mon Sep 17 00:00:00 2001 From: whitequark Date: Sun, 19 Jul 2015 12:08:26 +0300 Subject: [PATCH 116/369] Teach closures to LocalAccessValidator. --- artiq/compiler/validators/local_access.py | 87 +++++++++++++++-------- lit-test/compiler/local_access/invalid.py | 7 ++ 2 files changed, 64 insertions(+), 30 deletions(-) diff --git a/artiq/compiler/validators/local_access.py b/artiq/compiler/validators/local_access.py index 4f2b4ffd0..ee43b30a7 100644 --- a/artiq/compiler/validators/local_access.py +++ b/artiq/compiler/validators/local_access.py @@ -73,27 +73,38 @@ class LocalAccessValidator: # 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 + if isinstance(insn, (ir.SetLocal, ir.GetLocal)) and \ "." not in insn.var_name: env, var_name = insn.environment(), insn.var_name - assert env in block_state - assert var_name in block_state[env] - if isinstance(insn, ir.SetLocal): - # We've just initialized it. - block_state[env][var_name] = True - else: # isinstance(insn, ir.GetLocal) + # 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 + 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)) + + if isinstance(insn, ir.Closure): + env = insn.environment() + for var_name in block_state[env]: if not block_state[env][var_name]: - # Oops, accessing it uninitialized. Find out where - # the uninitialized state comes from. - pred_at_fault = None - for pred, pred_state in zip(forward_edge_preds, pred_states): - if not pred_state[env][var_name]: - pred_at_fault = pred - assert pred_at_fault is not None - - # Report the error. - self._uninitialized_access(insn, pred_at_fault) + # A closure would capture this variable while it is not always + # initialized. Note that this check is transitive. + self._uninitialized_access(insn, var_name, + pred_at_fault(env, var_name)) # Save the state. state[block] = block_state @@ -103,19 +114,35 @@ class LocalAccessValidator: for block in func.basic_blocks: traverse(block) - def _uninitialized_access(self, insn, pred_at_fault): - uninitialized_loc = None - for pred_insn in reversed(pred_at_fault.instructions): - if pred_insn.loc is not None: - uninitialized_loc = pred_insn.loc.begin() - break - assert uninitialized_loc is not None + def _uninitialized_access(self, insn, var_name, pred_at_fault): + if pred_at_fault is not None: + uninitialized_loc = None + for pred_insn in reversed(pred_at_fault.instructions): + if pred_insn.loc is not None: + uninitialized_loc = pred_insn.loc.begin() + break + 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) - note = diagnostic.Diagnostic("note", - "variable is not initialized when control flows from this point", {}, - uninitialized_loc) - diag = diagnostic.Diagnostic("error", - "variable '{name}' is not always initialized at this point", - {"name": insn.var_name}, - insn.loc, notes=[note]) self.engine.process(diag) diff --git a/lit-test/compiler/local_access/invalid.py b/lit-test/compiler/local_access/invalid.py index 94561d2d6..789285d51 100644 --- a/lit-test/compiler/local_access/invalid.py +++ b/lit-test/compiler/local_access/invalid.py @@ -18,3 +18,10 @@ else: t = 1 # CHECK-L: ${LINE:+1}: error: variable 't' is not always initialized -t + +# CHECK-L: ${LINE:+1}: error: variable 't' can be captured in a closure uninitialized +lambda: t + +# CHECK-L: ${LINE:+1}: error: variable 't' can be captured in a closure uninitialized +def f(): + return t From c6cd318f19c7273b5a89b08dd8016c63bccb82db Mon Sep 17 00:00:00 2001 From: whitequark Date: Sun, 19 Jul 2015 16:32:33 +0300 Subject: [PATCH 117/369] Fix artiq.compiler.ir.BasicBlock.__repr__. --- artiq/compiler/ir.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/artiq/compiler/ir.py b/artiq/compiler/ir.py index 89e1f4b1a..baa753647 100644 --- a/artiq/compiler/ir.py +++ b/artiq/compiler/ir.py @@ -341,7 +341,7 @@ class BasicBlock(NamedValue): return "\n".join(lines) def __repr__(self): - return "".format(self.name) + return "".format(repr(self.name)) class Argument(NamedValue): """ From 6f11fa6bb1a9eb0dcd51d065532429f90b5a6e11 Mon Sep 17 00:00:00 2001 From: whitequark Date: Tue, 21 Jul 2015 04:54:34 +0300 Subject: [PATCH 118/369] Add conversion to LLVM IR (except handling of exception handling). --- artiq/compiler/ir.py | 10 +- artiq/compiler/module.py | 11 +- artiq/compiler/testbench/irgen.py | 2 +- artiq/compiler/testbench/llvmgen.py | 18 + artiq/compiler/transforms/__init__.py | 3 +- ...{ir_generator.py => artiq_ir_generator.py} | 14 +- artiq/compiler/transforms/inferencer.py | 5 +- .../compiler/transforms/llvm_ir_generator.py | 408 ++++++++++++++++++ 8 files changed, 456 insertions(+), 15 deletions(-) create mode 100644 artiq/compiler/testbench/llvmgen.py rename artiq/compiler/transforms/{ir_generator.py => artiq_ir_generator.py} (99%) create mode 100644 artiq/compiler/transforms/llvm_ir_generator.py diff --git a/artiq/compiler/ir.py b/artiq/compiler/ir.py index baa753647..9d55cb069 100644 --- a/artiq/compiler/ir.py +++ b/artiq/compiler/ir.py @@ -19,10 +19,16 @@ 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, inner): super().__init__("option", {"inner": inner}) +def is_option(typ): + return isinstance(typ, TOption) + class Value: """ An SSA value that keeps track of its uses. @@ -571,7 +577,7 @@ class GetAttr(Instruction): def opcode(self): return "getattr({})".format(repr(self.attr)) - def env(self): + def object(self): return self.operands[0] class SetAttr(Instruction): @@ -600,7 +606,7 @@ class SetAttr(Instruction): def opcode(self): return "setattr({})".format(repr(self.attr)) - def env(self): + def object(self): return self.operands[0] def value(self): diff --git a/artiq/compiler/module.py b/artiq/compiler/module.py index 22ae58577..e1eb53be6 100644 --- a/artiq/compiler/module.py +++ b/artiq/compiler/module.py @@ -18,9 +18,10 @@ class Module: int_monomorphizer = transforms.IntMonomorphizer(engine=engine) monomorphism_validator = validators.MonomorphismValidator(engine=engine) escape_validator = validators.EscapeValidator(engine=engine) - ir_generator = transforms.IRGenerator(engine=engine, module_name=self.name) + artiq_ir_generator = transforms.ARTIQIRGenerator(engine=engine, module_name=self.name) dead_code_eliminator = transforms.DeadCodeEliminator(engine=engine) local_access_validator = validators.LocalAccessValidator(engine=engine) + llvm_ir_generator = transforms.LLVMIRGenerator(engine=engine, module_name=self.name) self.parsetree, self.comments = parse_buffer(source_buffer, engine=engine) self.typedtree = asttyped_rewriter.visit(self.parsetree) @@ -30,9 +31,11 @@ class Module: inferencer.visit(self.typedtree) monomorphism_validator.visit(self.typedtree) escape_validator.visit(self.typedtree) - self.ir = ir_generator.visit(self.typedtree) - dead_code_eliminator.process(self.ir) - local_access_validator.process(self.ir) + self.artiq_ir = artiq_ir_generator.visit(self.typedtree) + dead_code_eliminator.process(self.artiq_ir) + local_access_validator.process(self.artiq_ir) + llvm_ir_generator.process(self.artiq_ir) + self.llvm_ir = llvm_ir_generator.llmodule @classmethod def from_string(cls, source_string, name="input.py", first_line=1, engine=None): diff --git a/artiq/compiler/testbench/irgen.py b/artiq/compiler/testbench/irgen.py index f2701a301..20e9334ca 100644 --- a/artiq/compiler/testbench/irgen.py +++ b/artiq/compiler/testbench/irgen.py @@ -12,7 +12,7 @@ def main(): engine.process = process_diagnostic mod = Module.from_string("".join(fileinput.input()).expandtabs(), engine=engine) - for fn in mod.ir: + for fn in mod.artiq_ir: print(fn) if __name__ == "__main__": diff --git a/artiq/compiler/testbench/llvmgen.py b/artiq/compiler/testbench/llvmgen.py new file mode 100644 index 000000000..bb3d6ec3d --- /dev/null +++ b/artiq/compiler/testbench/llvmgen.py @@ -0,0 +1,18 @@ +import sys, fileinput +from pythonparser import diagnostic +from .. import Module + +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.from_string("".join(fileinput.input()).expandtabs(), engine=engine) + print(mod.llvm_ir) + +if __name__ == "__main__": + main() diff --git a/artiq/compiler/transforms/__init__.py b/artiq/compiler/transforms/__init__.py index 64dca0906..576ec7e10 100644 --- a/artiq/compiler/transforms/__init__.py +++ b/artiq/compiler/transforms/__init__.py @@ -1,5 +1,6 @@ from .asttyped_rewriter import ASTTypedRewriter from .inferencer import Inferencer from .int_monomorphizer import IntMonomorphizer -from .ir_generator import IRGenerator +from .artiq_ir_generator import ARTIQIRGenerator from .dead_code_eliminator import DeadCodeEliminator +from .llvm_ir_generator import LLVMIRGenerator diff --git a/artiq/compiler/transforms/ir_generator.py b/artiq/compiler/transforms/artiq_ir_generator.py similarity index 99% rename from artiq/compiler/transforms/ir_generator.py rename to artiq/compiler/transforms/artiq_ir_generator.py index 776a226d0..77f90b336 100644 --- a/artiq/compiler/transforms/ir_generator.py +++ b/artiq/compiler/transforms/artiq_ir_generator.py @@ -1,5 +1,5 @@ """ -:class:`IRGenerator` transforms typed AST into ARTIQ intermediate +: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 @@ -25,9 +25,9 @@ def _extract_loc(node): # 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 IRGenerator(algorithm.Visitor): +class ARTIQIRGenerator(algorithm.Visitor): """ - :class:`IRGenerator` contains a lot of internal state, + :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: @@ -255,8 +255,7 @@ class IRGenerator(algorithm.Visitor): def visit_Pass(self, node): # Insert a dummy instruction so that analyses which extract # locations from CFG have something to use. - self.append(ir.Arith(ast.Add(loc=None), - ir.Constant(0, self._size_type), ir.Constant(0, self._size_type))) + self.append(ir.Builtin("nop", [], builtins.TNone())) def visit_Assign(self, node): try: @@ -367,12 +366,13 @@ class IRGenerator(algorithm.Visitor): 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)) - phi.add_incoming(ir.Constant(0, phi.type), head) + phi.add_incoming(ir.Constant(0, phi.type), prehead) cond = self.append(ir.Compare(ast.Lt(loc=None), phi, length)) break_block = self.add_block("for.break") @@ -842,6 +842,8 @@ class IRGenerator(algorithm.Visitor): def visit_BinOpT(self, node): if builtins.is_numeric(node.type): + # TODO: check for division by zero + # TODO: check for shift by too many bits 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) diff --git a/artiq/compiler/transforms/inferencer.py b/artiq/compiler/transforms/inferencer.py index 2e34c4e57..3dd95aee0 100644 --- a/artiq/compiler/transforms/inferencer.py +++ b/artiq/compiler/transforms/inferencer.py @@ -335,9 +335,12 @@ class Inferencer(algorithm.Visitor): return list_.type, left.type, right.type else: return self._coerce_numeric((left, right), lambda typ: (typ, typ, typ)) - elif isinstance(op, (ast.Div, ast.FloorDiv, ast.Mod, ast.Pow, ast.Sub)): + 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(), typ, typ)) else: # MatMult diag = diagnostic.Diagnostic("error", "operator '{op}' is not supported", {"op": op.loc.source()}, diff --git a/artiq/compiler/transforms/llvm_ir_generator.py b/artiq/compiler/transforms/llvm_ir_generator.py new file mode 100644 index 000000000..416c87a1f --- /dev/null +++ b/artiq/compiler/transforms/llvm_ir_generator.py @@ -0,0 +1,408 @@ +""" +:class:`LLVMIRGenerator` transforms ARTIQ intermediate representation +into LLVM intermediate representation. +""" + +import llvmlite.ir as ll +from pythonparser import ast +from .. import types, builtins, ir + +class LLVMIRGenerator: + def __init__(self, engine, module_name, context=ll.Context()): + self.engine = engine + self.llcontext = context + self.llmodule = ll.Module(context=self.llcontext, name=module_name) + self.llfunction = None + self.llmap = {} + self.fixups = [] + + def llty_of_type(self, typ, for_alloc=False, for_return=False): + if types.is_tuple(typ): + return ll.LiteralStructType([self.llty_of_type(eltty) for eltty in typ.elts]) + elif types.is_function(typ): + envarg = ll.IntType(8).as_pointer + llty = ll.FunctionType(args=[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=self.llty_of_type(typ.ret, for_return=True)) + return llty.as_pointer() + elif builtins.is_none(typ): + if for_return: + return ll.VoidType() + else: + return ll.LiteralStructType([]) + elif builtins.is_bool(typ): + return ll.IntType(1) + elif builtins.is_int(typ): + return ll.IntType(builtins.get_int_width(typ)) + elif builtins.is_float(typ): + return ll.DoubleType() + elif builtins.is_list(typ): + lleltty = self.llty_of_type(builtins.get_iterable_elt(typ)) + return ll.LiteralStructType([ll.IntType(32), lleltty.as_pointer()]) + elif builtins.is_range(typ): + lleltty = self.llty_of_type(builtins.get_iterable_elt(typ)) + return ll.LiteralStructType([lleltty, lleltty, lleltty]) + elif builtins.is_exception(typ): + # TODO: hack before EH is working + return ll.LiteralStructType([]) + elif ir.is_basic_block(typ): + return ll.LabelType() + elif ir.is_option(typ): + return ll.LiteralStructType([ll.IntType(1), self.llty_of_type(typ.params["inner"])]) + elif ir.is_environment(typ): + llty = ll.LiteralStructType([self.llty_of_type(typ.params[name]) + for name in typ.params]) + if for_alloc: + return llty + else: + return llty.as_pointer() + else: + assert False + + def llconst_of_const(self, const): + llty = self.llty_of_type(const.type) + if const.value is None: + 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) + else: + assert False + + def map(self, value): + if isinstance(value, (ir.Instruction, ir.BasicBlock)): + return self.llmap[value] + elif isinstance(value, ir.Constant): + return self.llconst_of_const(value) + else: + assert False + + def process(self, functions): + for func in functions: + self.process_function(func) + + def process_function(self, func): + llargtys = [] + for arg in func.arguments: + llargtys.append(self.llty_of_type(arg.type)) + llfunty = ll.FunctionType(args=llargtys, + return_type=self.llty_of_type(func.type.ret, for_return=True)) + + try: + self.llfunction = ll.Function(self.llmodule, llfunty, func.name) + self.llmap = {} + self.llbuilder = ll.IRBuilder() + self.fixups = [] + + # First, create all basic blocks. + for block in func.basic_blocks: + llblock = self.llfunction.append_basic_block(block.name) + self.llmap[block] = llblock + + # Second, 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 + + # Third, fixup phis. + for fixup in self.fixups: + fixup() + finally: + self.llfunction = None + self.llmap = None + self.fixups = [] + + def process_Phi(self, insn): + llinsn = self.llbuilder.phi(self.llty_of_type(insn.type), name=insn.name) + def fixup(): + for value, block in insn.incoming(): + llinsn.add_incoming(self.map(value), self.map(block)) + self.fixups.append(fixup) + return llinsn + + def process_Alloc(self, insn): + if ir.is_environment(insn.type): + return self.llbuilder.alloca(self.llty_of_type(insn.type, for_alloc=True), + name=insn.name) + elif builtins.is_list(insn.type): + llsize = self.map(insn.operands[0]) + llvalue = ll.Constant(self.llty_of_type(insn.type), ll.Undefined) + llvalue = self.llbuilder.insert_value(llvalue, llsize, 0) + llalloc = self.llbuilder.alloca(self.llty_of_type(builtins.get_iterable_elt(insn.type)), + size=llsize) + llvalue = self.llbuilder.insert_value(llvalue, llalloc, 1, name=insn.name) + return llvalue + elif builtins.is_mutable(insn.type): + assert False + else: # immutable + 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 + + def llindex(self, index): + return ll.Constant(ll.IntType(32), index) + + 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)]) + else: + outer_index = list(env_ty.params.keys()).index(".outer") + llptr = self.llbuilder.gep(llenv, [self.llindex(0), self.llindex(outer_index)]) + llouterenv = self.llbuilder.load(llptr) + return self.llptr_to_var(llouterenv, env_ty.params[".outer"], var_name) + + def process_GetLocal(self, insn): + env = insn.environment() + llptr = self.llptr_to_var(self.map(env), env.type, insn.var_name) + return self.llbuilder.load(llptr) + + def process_SetLocal(self, insn): + env = insn.environment() + llptr = self.llptr_to_var(self.map(env), env.type, insn.var_name) + return self.llbuilder.store(self.map(insn.value()), llptr) + + def attr_index(self, insn): + return list(insn.object().type.attributes.keys()).index(insn.attr) + + def process_GetAttr(self, insn): + if types.is_tuple(insn.object().type): + return self.llbuilder.extract_value(self.map(insn.object()), self.attr_index(insn), + name=insn.name) + elif not builtins.is_mutable(insn.object().type): + return self.llbuilder.extract_value(self.map(insn.object()), self.attr_index(insn), + name=insn.name) + else: + llptr = self.llbuilder.gep(self.map(insn.object()), + [self.llindex(0), self.llindex(self.attr_index(insn))], + name=insn.name) + return self.llbuilder.load(llptr) + + def process_SetAttr(self, insn): + assert builtins.is_mutable(insns.object().type) + llptr = self.llbuilder.gep(self.map(insn.object()), + [self.llindex(0), self.llindex(self.attr_index(insn))], + name=insn.name) + return self.llbuilder.store(llptr, self.map(insn.value())) + + def process_GetElem(self, insn): + llelts = self.llbuilder.extract_value(self.map(insn.list()), 1) + llelt = self.llbuilder.gep(llelts, [self.map(insn.index())], + inbounds=True) + return self.llbuilder.load(llelt) + + def process_SetElem(self, insn): + llelts = self.llbuilder.extract_value(self.map(insn.list()), 1) + llelt = self.llbuilder.gep(llelts, [self.map(insn.index())], + 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 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 process_Arith(self, insn): + if isinstance(insn.op, ast.Add): + if builtins.is_float(insn.type): + return self.llbuilder.fadd(self.map(insn.lhs()), self.map(insn.rhs()), + name=insn.name) + 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): + return self.llbuilder.fsub(self.map(insn.lhs()), self.map(insn.rhs()), + name=insn.name) + 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): + return self.llbuilder.fmul(self.map(insn.lhs()), self.map(insn.rhs()), + name=insn.name) + 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): + return self.llbuilder.fdiv(self.map(insn.lhs()), self.map(insn.rhs()), + name=insn.name) + 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)) + return self.llbuilder.fdiv(lllhs, llrhs, + name=insn.name) + elif isinstance(insn.op, ast.FloorDiv): + if builtins.is_float(insn.type): + llvalue = self.llbuilder.fdiv(self.map(insn.lhs()), self.map(insn.rhs())) + llfnty = ll.FunctionType(ll.DoubleType(), [ll.DoubleType()]) + llfn = ll.Function(self.llmodule, llfnty, "llvm.round.f64") + return self.llbuilder.call(llfn, [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): + if builtins.is_float(insn.type): + return self.llbuilder.frem(self.map(insn.lhs()), self.map(insn.rhs()), + name=insn.name) + else: + return self.llbuilder.srem(self.map(insn.lhs()), self.map(insn.rhs()), + name=insn.name) + elif isinstance(insn.op, ast.Pow): + if builtins.is_float(insn.type): + llfnty = ll.FunctionType(ll.DoubleType(), [ll.DoubleType(), ll.DoubleType()]) + llfn = ll.Function(self.llmodule, llfnty, "llvm.pow.f64") + return self.llbuilder.call(llfn, [self.map(insn.lhs()), self.map(insn.rhs())], + name=insn.name) + else: + llrhs = self.llbuilder.trunc(self.map(insn.rhs()), ll.IntType(32)) + llfnty = ll.FunctionType(ll.DoubleType(), [ll.DoubleType(), ll.IntType(32)]) + llfn = ll.Function(self.llmodule, llfnty, "llvm.powi.f64") + llvalue = self.llbuilder.call(llfn, [self.map(insn.lhs()), llrhs]) + return self.llbuilder.fptosi(llvalue, self.llty_of_type(insn.type), + name=insn.name) + elif isinstance(insn.op, ast.LShift): + return self.llbuilder.shl(self.map(insn.lhs()), self.map(insn.rhs()), + name=insn.name) + elif isinstance(insn.op, ast.RShift): + return self.llbuilder.ashr(self.map(insn.lhs()), self.map(insn.rhs()), + 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): + op = '==' + elif isinstance(insn.op, ast.NotEq): + 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 + + if builtins.is_float(insn.lhs().type): + return self.llbuilder.fcmp_ordered(op, self.map(insn.lhs()), self.map(insn.rhs()), + name=insn.name) + else: + return self.llbuilder.icmp_signed(op, self.map(insn.lhs()), self.map(insn.rhs()), + name=insn.name) + + def process_Builtin(self, insn): + if insn.op == "nop": + fn = ll.Function(self.llmodule, ll.FunctionType(ll.VoidType(), []), "llvm.donothing") + return self.llbuilder.call(fn, []) + elif insn.op == "unwrap": + 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, + name=insn.name) + elif insn.op == "round": + llfnty = ll.FunctionType(ll.DoubleType(), [ll.DoubleType()]) + llfn = ll.Function(self.llmodule, llfnty, "llvm.round.f64") + return self.llbuilder.call(llfn, [llvalue], + 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)]) + llouterenv = self.llbuilder.load(llptr) + 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": + lst, = insn.operands + return self.llbuilder.extract_value(self.map(lst), 0) + # elif insn.op == "exncast": + else: + assert False + + # def process_Closure(self, insn): + # pass + + # def process_Call(self, insn): + # pass + + def process_Select(self, insn): + return self.llbuilder.select(self.map(insn.cond()), + self.map(insn.lhs()), self.map(insn.rhs())) + + def process_Branch(self, insn): + return self.llbuilder.branch(self.map(insn.target())) + + def process_BranchIf(self, insn): + return self.llbuilder.cbranch(self.map(insn.condition()), + self.map(insn.if_true()), self.map(insn.if_false())) + + # def process_IndirectBranch(self, insn): + # pass + + def process_Return(self, insn): + if builtins.is_none(insn.type): + return self.llbuilder.ret_void() + else: + return self.llbuilder.ret(self.llmap[insn.value()]) + + def process_Unreachable(self, insn): + return self.llbuilder.unreachable() + + def process_Raise(self, insn): + # TODO: hack before EH is working + llfnty = ll.FunctionType(ll.VoidType(), []) + llfn = ll.Function(self.llmodule, llfnty, "llvm.abort") + llinsn = self.llbuilder.call(llfn, [], + name=insn.name) + self.llbuilder.unreachable() + return llinsn + + # def process_Invoke(self, insn): + # pass + + # def process_LandingPad(self, insn): + # pass + From e299801c0f68cc2060b2b0b596fc34c5e3d07ddd Mon Sep 17 00:00:00 2001 From: whitequark Date: Tue, 21 Jul 2015 13:16:18 +0300 Subject: [PATCH 119/369] LocalAccessValidator: fix validation of closures with no outer variables. --- artiq/compiler/validators/local_access.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/artiq/compiler/validators/local_access.py b/artiq/compiler/validators/local_access.py index ee43b30a7..c86f3e46b 100644 --- a/artiq/compiler/validators/local_access.py +++ b/artiq/compiler/validators/local_access.py @@ -99,12 +99,14 @@ class LocalAccessValidator: if isinstance(insn, ir.Closure): env = insn.environment() - for var_name in block_state[env]: - if not block_state[env][var_name]: - # A closure would capture this variable while it is not always - # initialized. Note that this check is transitive. - self._uninitialized_access(insn, var_name, - pred_at_fault(env, var_name)) + # 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]: + # A closure would capture this variable while it is not always + # initialized. Note that this check is transitive. + self._uninitialized_access(insn, var_name, + pred_at_fault(env, var_name)) # Save the state. state[block] = block_state From ec9d40b04f53dd5f36dc21d1a03d2e6ee8fda065 Mon Sep 17 00:00:00 2001 From: whitequark Date: Tue, 21 Jul 2015 13:45:27 +0300 Subject: [PATCH 120/369] Add LLVM IR generation for function calls. --- artiq/compiler/ir.py | 4 +- .../compiler/transforms/artiq_ir_generator.py | 6 +- .../compiler/transforms/llvm_ir_generator.py | 76 +++++++++++++------ 3 files changed, 59 insertions(+), 27 deletions(-) diff --git a/artiq/compiler/ir.py b/artiq/compiler/ir.py index 9d55cb069..55313b7ed 100644 --- a/artiq/compiler/ir.py +++ b/artiq/compiler/ir.py @@ -467,7 +467,7 @@ class TEnvironment(types.TMono): def is_environment(typ): return isinstance(typ, TEnvironment) -class EnvironmentArgument(NamedValue): +class EnvironmentArgument(Argument): """ A function argument specifying an outer environment. """ @@ -799,7 +799,7 @@ class Call(Instruction): def opcode(self): return "call" - def function(self): + def target_function(self): return self.operands[0] def arguments(self): diff --git a/artiq/compiler/transforms/artiq_ir_generator.py b/artiq/compiler/transforms/artiq_ir_generator.py index 77f90b336..f449b4a2f 100644 --- a/artiq/compiler/transforms/artiq_ir_generator.py +++ b/artiq/compiler/transforms/artiq_ir_generator.py @@ -174,7 +174,7 @@ class ARTIQIRGenerator(algorithm.Visitor): optargs = [] for arg_name in typ.optargs: - optargs.append(ir.Argument(ir.TSSAOption(typ.optargs[arg_name]), "arg." + arg_name)) + optargs.append(ir.Argument(ir.TOption(typ.optargs[arg_name]), "arg." + arg_name)) func = ir.Function(typ, ".".join(self.name), [env_arg] + args + optargs) self.functions.append(func) @@ -1189,11 +1189,11 @@ class ARTIQIRGenerator(algorithm.Visitor): optarg_typ = ir.TOption(typ.optargs[optarg_name]) for keyword in node.keywords: if keyword.arg == optarg_name: - value = self.append(ir.Alloc(optarg_typ, [self.visit(keyword.value)])) + value = self.append(ir.Alloc([self.visit(keyword.value)], optarg_typ)) args.append(value) break else: - value = self.append(ir.Alloc(optarg_typ, [])) + value = self.append(ir.Alloc([], optarg_typ)) args.append(value) if self.unwind_target is None: diff --git a/artiq/compiler/transforms/llvm_ir_generator.py b/artiq/compiler/transforms/llvm_ir_generator.py index 416c87a1f..dcbcb3350 100644 --- a/artiq/compiler/transforms/llvm_ir_generator.py +++ b/artiq/compiler/transforms/llvm_ir_generator.py @@ -16,18 +16,21 @@ class LLVMIRGenerator: self.llmap = {} self.fixups = [] - def llty_of_type(self, typ, for_alloc=False, for_return=False): + def llty_of_type(self, typ, bare=False, for_return=False): if types.is_tuple(typ): return ll.LiteralStructType([self.llty_of_type(eltty) for eltty in typ.elts]) elif types.is_function(typ): - envarg = ll.IntType(8).as_pointer + envarg = ll.IntType(8).as_pointer() llty = ll.FunctionType(args=[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=self.llty_of_type(typ.ret, for_return=True)) - return llty.as_pointer() + if bare: + return llty + else: + return ll.LiteralStructType([envarg, llty.as_pointer()]) elif builtins.is_none(typ): if for_return: return ll.VoidType() @@ -55,7 +58,7 @@ class LLVMIRGenerator: elif ir.is_environment(typ): llty = ll.LiteralStructType([self.llty_of_type(typ.params[name]) for name in typ.params]) - if for_alloc: + if bare: return llty else: return llty.as_pointer() @@ -76,10 +79,17 @@ class LLVMIRGenerator: assert False def map(self, value): - if isinstance(value, (ir.Instruction, ir.BasicBlock)): + 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): + llfun = self.llmodule.get_global(value.name) + if llfun is None: + return ll.Function(self.llmodule, self.llty_of_type(value.type, bare=True), + value.name) + else: + return llfun else: assert False @@ -88,24 +98,30 @@ class LLVMIRGenerator: self.process_function(func) def process_function(self, func): - llargtys = [] - for arg in func.arguments: - llargtys.append(self.llty_of_type(arg.type)) - llfunty = ll.FunctionType(args=llargtys, - return_type=self.llty_of_type(func.type.ret, for_return=True)) - try: - self.llfunction = ll.Function(self.llmodule, llfunty, func.name) + self.llfunction = self.llmodule.get_global(func.name) + if self.llfunction is None: + llargtys = [] + for arg in func.arguments: + llargtys.append(self.llty_of_type(arg.type)) + llfunty = ll.FunctionType(args=llargtys, + return_type=self.llty_of_type(func.type.ret, for_return=True)) + self.llfunction = ll.Function(self.llmodule, llfunty, func.name) + self.llmap = {} self.llbuilder = ll.IRBuilder() self.fixups = [] - # First, create all basic blocks. + # First, map arguments. + for arg, llarg in zip(func.arguments, self.llfunction.args): + 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 - # Second, translate all instructions. + # Third, translate all instructions. for block in func.basic_blocks: self.llbuilder.position_at_end(self.llmap[block]) for insn in block.instructions: @@ -113,7 +129,7 @@ class LLVMIRGenerator: assert llinsn is not None self.llmap[insn] = llinsn - # Third, fixup phis. + # Fourth, fixup phis. for fixup in self.fixups: fixup() finally: @@ -131,7 +147,7 @@ class LLVMIRGenerator: def process_Alloc(self, insn): if ir.is_environment(insn.type): - return self.llbuilder.alloca(self.llty_of_type(insn.type, for_alloc=True), + return self.llbuilder.alloca(self.llty_of_type(insn.type, bare=True), name=insn.name) elif builtins.is_list(insn.type): llsize = self.map(insn.operands[0]) @@ -171,7 +187,14 @@ class LLVMIRGenerator: def process_SetLocal(self, insn): env = insn.environment() llptr = self.llptr_to_var(self.map(env), env.type, insn.var_name) - return self.llbuilder.store(self.map(insn.value()), llptr) + llvalue = self.map(insn.value()) + 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 isinstance(insn.value(), ir.EnvironmentArgument) + llvalue = self.llbuilder.bitcast(llvalue, llptr.type.pointee) + return self.llbuilder.store(llvalue, llptr) def attr_index(self, insn): return list(insn.object().type.attributes.keys()).index(insn.attr) @@ -362,11 +385,20 @@ class LLVMIRGenerator: else: assert False - # def process_Closure(self, insn): - # pass + def process_Closure(self, insn): + llvalue = ll.Constant(self.llty_of_type(insn.target_function.type), ll.Undefined) + llenv = self.llbuilder.bitcast(self.map(insn.environment()), ll.IntType(8).as_pointer()) + llvalue = self.llbuilder.insert_value(llvalue, llenv, 0) + llvalue = self.llbuilder.insert_value(llvalue, self.map(insn.target_function), 1, + name=insn.name) + return llvalue - # def process_Call(self, insn): - # pass + def process_Call(self, insn): + llclosure, llargs = self.map(insn.target_function()), map(self.map, insn.arguments()) + llenv = self.llbuilder.extract_value(llclosure, 0) + llfun = self.llbuilder.extract_value(llclosure, 1) + return self.llbuilder.call(llfun, [llenv] + list(llargs), + name=insn.name) def process_Select(self, insn): return self.llbuilder.select(self.map(insn.cond()), @@ -383,7 +415,7 @@ class LLVMIRGenerator: # pass def process_Return(self, insn): - if builtins.is_none(insn.type): + if builtins.is_none(insn.value().type): return self.llbuilder.ret_void() else: return self.llbuilder.ret(self.llmap[insn.value()]) From e58b811d6d9732d1d4e89129fc8ff1766e4bf00c Mon Sep 17 00:00:00 2001 From: whitequark Date: Tue, 21 Jul 2015 14:05:07 +0300 Subject: [PATCH 121/369] Fix tests broken by fixed FloorDiv. --- lit-test/compiler/inferencer/coerce.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lit-test/compiler/inferencer/coerce.py b/lit-test/compiler/inferencer/coerce.py index 3b0190c49..04fe42bbc 100644 --- a/lit-test/compiler/inferencer/coerce.py +++ b/lit-test/compiler/inferencer/coerce.py @@ -19,8 +19,8 @@ [1] * 2 # CHECK-L: [1:int(width='l)]:list(elt=int(width='l)) * 2:int(width='m):list(elt=int(width='l)) -1 / 2 -# CHECK-L: 1:int(width='n):int(width='o) / 2:int(width='p):int(width='o):int(width='o) +1 // 2 +# CHECK-L: 1:int(width='n):int(width='o) // 2:int(width='p):int(width='o):int(width='o) 1 + 1.0 # CHECK-L: 1:int(width='q):float + 1.0:float:float From 64d2604aa8416fea585a525400177a82b07b4bd6 Mon Sep 17 00:00:00 2001 From: whitequark Date: Tue, 21 Jul 2015 14:12:27 +0300 Subject: [PATCH 122/369] Tolerate assertion failures in tests when looking for diagnostics. --- artiq/compiler/testbench/module.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/artiq/compiler/testbench/module.py b/artiq/compiler/testbench/module.py index b5eab3861..1882260bf 100644 --- a/artiq/compiler/testbench/module.py +++ b/artiq/compiler/testbench/module.py @@ -5,11 +5,13 @@ from .. import Module 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())) if diag.level in ("fatal", "error"): @@ -18,8 +20,11 @@ def main(): engine = diagnostic.Engine() engine.process = process_diagnostic - mod = Module.from_string("".join(fileinput.input()).expandtabs(), engine=engine) - print(repr(mod)) + try: + mod = Module.from_string("".join(fileinput.input()).expandtabs(), engine=engine) + print(repr(mod)) + except: + if not diag: raise if __name__ == "__main__": main() From 49ece6a12a3712344839b0cf1fcc664f2a742973 Mon Sep 17 00:00:00 2001 From: whitequark Date: Tue, 21 Jul 2015 14:27:48 +0300 Subject: [PATCH 123/369] Add support for string literals. --- artiq/compiler/builtins.py | 14 +++++++++++-- .../compiler/transforms/artiq_ir_generator.py | 5 ++++- .../compiler/transforms/asttyped_rewriter.py | 6 +++++- .../compiler/transforms/llvm_ir_generator.py | 18 ++++++++++++---- artiq/compiler/validators/escape.py | 21 +++++++++++-------- lit-test/compiler/inferencer/unify.py | 2 ++ 6 files changed, 49 insertions(+), 17 deletions(-) diff --git a/artiq/compiler/builtins.py b/artiq/compiler/builtins.py index 8ca095b81..4b914063f 100644 --- a/artiq/compiler/builtins.py +++ b/artiq/compiler/builtins.py @@ -50,6 +50,10 @@ class TFloat(types.TMono): def one(): return 1.0 +class TStr(types.TMono): + def __init__(self): + super().__init__("str") + class TList(types.TMono): def __init__(self, elt=None): if elt is None: @@ -88,6 +92,9 @@ def fn_int(): def fn_float(): return types.TConstructor("float") +def fn_str(): + return types.TConstructor("str") + def fn_list(): return types.TConstructor("list") @@ -133,6 +140,9 @@ def get_int_width(typ): def is_float(typ): return types.is_mono(typ, "float") +def is_str(typ): + return types.is_mono(typ, "str") + def is_numeric(typ): typ = typ.find() return isinstance(typ, types.TMono) and \ @@ -167,6 +177,6 @@ def is_collection(typ): return isinstance(typ, types.TTuple) or \ types.is_mono(typ, "list") -def is_mutable(typ): +def is_allocated(typ): return typ.fold(False, lambda accum, typ: - is_list(typ) or types.is_function(typ)) + is_list(typ) or is_str(typ) or types.is_function(typ)) diff --git a/artiq/compiler/transforms/artiq_ir_generator.py b/artiq/compiler/transforms/artiq_ir_generator.py index f449b4a2f..c18b987a2 100644 --- a/artiq/compiler/transforms/artiq_ir_generator.py +++ b/artiq/compiler/transforms/artiq_ir_generator.py @@ -565,6 +565,9 @@ class ARTIQIRGenerator(algorithm.Visitor): 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) @@ -1038,7 +1041,7 @@ class ARTIQIRGenerator(algorithm.Visitor): return result def _compare_pair_identity(self, op, lhs, rhs): - if builtins.is_mutable(lhs) and builtins.is_mutable(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: diff --git a/artiq/compiler/transforms/asttyped_rewriter.py b/artiq/compiler/transforms/asttyped_rewriter.py index 0c353bee6..df0b46d91 100644 --- a/artiq/compiler/transforms/asttyped_rewriter.py +++ b/artiq/compiler/transforms/asttyped_rewriter.py @@ -252,6 +252,11 @@ class ASTTypedRewriter(algorithm.Transformer): return asttyped.NumT(type=typ, n=node.n, loc=node.loc) + def visit_Str(self, node): + return asttyped.StrT(type=builtins.TStr(), + 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) @@ -411,7 +416,6 @@ class ASTTypedRewriter(algorithm.Transformer): visit_GeneratorExp = visit_unsupported visit_Set = visit_unsupported visit_SetComp = visit_unsupported - visit_Str = visit_unsupported visit_Starred = visit_unsupported visit_Yield = visit_unsupported visit_YieldFrom = visit_unsupported diff --git a/artiq/compiler/transforms/llvm_ir_generator.py b/artiq/compiler/transforms/llvm_ir_generator.py index dcbcb3350..717c2f169 100644 --- a/artiq/compiler/transforms/llvm_ir_generator.py +++ b/artiq/compiler/transforms/llvm_ir_generator.py @@ -18,7 +18,7 @@ class LLVMIRGenerator: def llty_of_type(self, typ, bare=False, for_return=False): if types.is_tuple(typ): - return ll.LiteralStructType([self.llty_of_type(eltty) for eltty in typ.elts]) + return ll.LiteralStructType([self.llty_of_type(eltty) for eltty in typ.find().elts]) elif types.is_function(typ): envarg = ll.IntType(8).as_pointer() llty = ll.FunctionType(args=[envarg] + @@ -42,6 +42,8 @@ class LLVMIRGenerator: return ll.IntType(builtins.get_int_width(typ)) elif builtins.is_float(typ): return ll.DoubleType() + elif builtins.is_str(typ): + return ll.IntType(8).as_pointer() elif builtins.is_list(typ): lleltty = self.llty_of_type(builtins.get_iterable_elt(typ)) return ll.LiteralStructType([ll.IntType(32), lleltty.as_pointer()]) @@ -75,6 +77,14 @@ class LLVMIRGenerator: return ll.Constant(llty, False) elif isinstance(const.value, (int, float)): return ll.Constant(llty, const.value) + elif isinstance(const.value, str): + as_bytes = const.value.encode('utf-8') + llstrty = ll.ArrayType(ll.IntType(8), len(as_bytes)) + llconst = ll.GlobalVariable(self.llmodule, llstrty, + name=self.llmodule.get_unique_name("str")) + llconst.global_constant = True + llconst.initializer = ll.Constant(llstrty, bytearray(as_bytes)) + return llconst.bitcast(ll.IntType(8).as_pointer()) else: assert False @@ -157,7 +167,7 @@ class LLVMIRGenerator: size=llsize) llvalue = self.llbuilder.insert_value(llvalue, llalloc, 1, name=insn.name) return llvalue - elif builtins.is_mutable(insn.type): + elif builtins.is_allocated(insn.type): assert False else: # immutable llvalue = ll.Constant(self.llty_of_type(insn.type), ll.Undefined) @@ -203,7 +213,7 @@ class LLVMIRGenerator: if types.is_tuple(insn.object().type): return self.llbuilder.extract_value(self.map(insn.object()), self.attr_index(insn), name=insn.name) - elif not builtins.is_mutable(insn.object().type): + elif not builtins.is_allocated(insn.object().type): return self.llbuilder.extract_value(self.map(insn.object()), self.attr_index(insn), name=insn.name) else: @@ -213,7 +223,7 @@ class LLVMIRGenerator: return self.llbuilder.load(llptr) def process_SetAttr(self, insn): - assert builtins.is_mutable(insns.object().type) + assert builtins.is_allocated(insns.object().type) llptr = self.llbuilder.gep(self.map(insn.object()), [self.llindex(0), self.llindex(self.attr_index(insn))], name=insn.name) diff --git a/artiq/compiler/validators/escape.py b/artiq/compiler/validators/escape.py index fd030eeef..603b97d7d 100644 --- a/artiq/compiler/validators/escape.py +++ b/artiq/compiler/validators/escape.py @@ -78,7 +78,7 @@ class RegionOf(algorithm.Visitor): # Value lives as long as the current scope, if it's mutable, # or else forever def visit_BinOpT(self, node): - if builtins.is_mutable(node.type): + if builtins.is_allocated(node.type): return self.youngest_region else: return None @@ -86,7 +86,7 @@ class RegionOf(algorithm.Visitor): # Value lives as long as the object/container, if it's mutable, # or else forever def visit_accessor(self, node): - if builtins.is_mutable(node.type): + if builtins.is_allocated(node.type): return self.visit(node.value) else: return None @@ -125,20 +125,23 @@ class RegionOf(algorithm.Visitor): visit_ListCompT = visit_allocating visit_SetT = visit_allocating visit_SetCompT = visit_allocating - visit_StrT = visit_allocating # Value lives forever def visit_immutable(self, node): - assert not builtins.is_mutable(node.type) + assert not builtins.is_allocated(node.type) return None - visit_CompareT = visit_immutable - visit_EllipsisT = visit_immutable visit_NameConstantT = visit_immutable visit_NumT = visit_immutable + 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): + return None + # Not implemented def visit_unimplemented(self, node): assert False @@ -212,7 +215,7 @@ class EscapeValidator(algorithm.Visitor): self.youngest_env = {} for name in node.typing_env: - if builtins.is_mutable(node.typing_env[name]): + if builtins.is_allocated(node.typing_env[name]): self.youngest_env[name] = Region(None) # not yet known else: self.youngest_env[name] = None # lives forever @@ -277,7 +280,7 @@ class EscapeValidator(algorithm.Visitor): self.visit_assignment(target, node.value) def visit_AugAssign(self, node): - if builtins.is_mutable(node.target.type): + if builtins.is_allocated(node.target.type): # If the target is mutable, op-assignment will allocate # in the youngest region. self.visit_assignment(node.target, node.value, is_aug_assign=True) @@ -295,7 +298,7 @@ class EscapeValidator(algorithm.Visitor): self.engine.process(diag) def visit_Raise(self, node): - if builtins.is_mutable(node.exc.type): + if builtins.is_allocated(node.exc.type): note = diagnostic.Diagnostic("note", "this expression has type {type}", {"type": types.TypePrinter().name(node.exc.type)}, diff --git a/lit-test/compiler/inferencer/unify.py b/lit-test/compiler/inferencer/unify.py index 04c8aecbd..48681fdcb 100644 --- a/lit-test/compiler/inferencer/unify.py +++ b/lit-test/compiler/inferencer/unify.py @@ -54,3 +54,5 @@ not 1 lambda x, y=1: x # CHECK-L: lambda x:'k, y:int(width='l)=1:int(width='l): x:'k:(x:'k, ?y:int(width='l))->'k +k = "x" +# CHECK-L: k:str From 7301a76d680533e90468fe54941ea68b3de17844 Mon Sep 17 00:00:00 2001 From: whitequark Date: Tue, 21 Jul 2015 17:10:31 +0300 Subject: [PATCH 124/369] Mark string constants as unnamed_addr. As a result they will be merged when possible. --- artiq/compiler/transforms/llvm_ir_generator.py | 1 + 1 file changed, 1 insertion(+) diff --git a/artiq/compiler/transforms/llvm_ir_generator.py b/artiq/compiler/transforms/llvm_ir_generator.py index 717c2f169..49859355a 100644 --- a/artiq/compiler/transforms/llvm_ir_generator.py +++ b/artiq/compiler/transforms/llvm_ir_generator.py @@ -83,6 +83,7 @@ class LLVMIRGenerator: llconst = ll.GlobalVariable(self.llmodule, llstrty, name=self.llmodule.get_unique_name("str")) llconst.global_constant = True + llconst.unnamed_addr = True llconst.initializer = ll.Constant(llstrty, bytearray(as_bytes)) return llconst.bitcast(ll.IntType(8).as_pointer()) else: From 8c9d9cb5a14ece74e33f4a0ecf299a1fed5e01b5 Mon Sep 17 00:00:00 2001 From: whitequark Date: Tue, 21 Jul 2015 19:48:44 +0300 Subject: [PATCH 125/369] Make compiler.testbench.llvmgen emit a main() function. --- artiq/compiler/testbench/llvmgen.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/artiq/compiler/testbench/llvmgen.py b/artiq/compiler/testbench/llvmgen.py index bb3d6ec3d..c32ceb98e 100644 --- a/artiq/compiler/testbench/llvmgen.py +++ b/artiq/compiler/testbench/llvmgen.py @@ -1,5 +1,6 @@ import sys, fileinput from pythonparser import diagnostic +from llvmlite import ir as ll from .. import Module def main(): @@ -11,8 +12,15 @@ def main(): engine = diagnostic.Engine() engine.process = process_diagnostic - mod = Module.from_string("".join(fileinput.input()).expandtabs(), engine=engine) - print(mod.llvm_ir) + 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__": main() From 9d20080624d2e2e96818f4865a2ba1eefb295fa0 Mon Sep 17 00:00:00 2001 From: whitequark Date: Tue, 21 Jul 2015 19:54:22 +0300 Subject: [PATCH 126/369] Use internal linkage for interior Python global values. --- artiq/compiler/transforms/llvm_ir_generator.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/artiq/compiler/transforms/llvm_ir_generator.py b/artiq/compiler/transforms/llvm_ir_generator.py index 49859355a..fa7364b51 100644 --- a/artiq/compiler/transforms/llvm_ir_generator.py +++ b/artiq/compiler/transforms/llvm_ir_generator.py @@ -84,6 +84,7 @@ class LLVMIRGenerator: name=self.llmodule.get_unique_name("str")) llconst.global_constant = True llconst.unnamed_addr = True + llconst.linkage = 'internal' llconst.initializer = ll.Constant(llstrty, bytearray(as_bytes)) return llconst.bitcast(ll.IntType(8).as_pointer()) else: @@ -118,6 +119,7 @@ class LLVMIRGenerator: llfunty = ll.FunctionType(args=llargtys, return_type=self.llty_of_type(func.type.ret, for_return=True)) self.llfunction = ll.Function(self.llmodule, llfunty, func.name) + self.llfunction.linkage = 'internal' self.llmap = {} self.llbuilder = ll.IRBuilder() From 0e7294db8d100b4b97c654d3ffe3c1bae60aba6b Mon Sep 17 00:00:00 2001 From: whitequark Date: Tue, 21 Jul 2015 19:57:18 +0300 Subject: [PATCH 127/369] Null-terminate all string literals. --- artiq/compiler/transforms/llvm_ir_generator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/artiq/compiler/transforms/llvm_ir_generator.py b/artiq/compiler/transforms/llvm_ir_generator.py index fa7364b51..8405c147c 100644 --- a/artiq/compiler/transforms/llvm_ir_generator.py +++ b/artiq/compiler/transforms/llvm_ir_generator.py @@ -78,7 +78,7 @@ class LLVMIRGenerator: elif isinstance(const.value, (int, float)): return ll.Constant(llty, const.value) elif isinstance(const.value, str): - as_bytes = const.value.encode('utf-8') + as_bytes = (const.value + '\0').encode('utf-8') llstrty = ll.ArrayType(ll.IntType(8), len(as_bytes)) llconst = ll.GlobalVariable(self.llmodule, llstrty, name=self.llmodule.get_unique_name("str")) From 1e851adf4f6803b5792ec8a08c4c47b56970b655 Mon Sep 17 00:00:00 2001 From: whitequark Date: Tue, 21 Jul 2015 22:32:10 +0300 Subject: [PATCH 128/369] Add a polymorphic print function. --- artiq/compiler/builtins.py | 3 + artiq/compiler/prelude.py | 1 + .../compiler/transforms/artiq_ir_generator.py | 146 ++++++++++++++---- artiq/compiler/transforms/inferencer.py | 13 ++ .../compiler/transforms/llvm_ir_generator.py | 55 ++++--- 5 files changed, 170 insertions(+), 48 deletions(-) diff --git a/artiq/compiler/builtins.py b/artiq/compiler/builtins.py index 4b914063f..dde80af12 100644 --- a/artiq/compiler/builtins.py +++ b/artiq/compiler/builtins.py @@ -116,6 +116,9 @@ def fn_len(): def fn_round(): return types.TBuiltinFunction("round") +def fn_print(): + return types.TBuiltinFunction("print") + def fn_syscall(): return types.TBuiltinFunction("syscall") diff --git a/artiq/compiler/prelude.py b/artiq/compiler/prelude.py index 98432b753..bed44af6c 100644 --- a/artiq/compiler/prelude.py +++ b/artiq/compiler/prelude.py @@ -17,5 +17,6 @@ def globals(): "ValueError": builtins.fn_ValueError(), "len": builtins.fn_len(), "round": builtins.fn_round(), + "print": builtins.fn_print(), "syscall": builtins.fn_syscall(), } diff --git a/artiq/compiler/transforms/artiq_ir_generator.py b/artiq/compiler/transforms/artiq_ir_generator.py index c18b987a2..715845b45 100644 --- a/artiq/compiler/transforms/artiq_ir_generator.py +++ b/artiq/compiler/transforms/artiq_ir_generator.py @@ -338,7 +338,7 @@ class ARTIQIRGenerator(algorithm.Visitor): 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=builtins.TInt(types.TValue(32))): if builtins.is_list(value.type): return self.append(ir.Builtin("len", [value], typ)) elif builtins.is_range(value.type): @@ -350,7 +350,7 @@ class ARTIQIRGenerator(algorithm.Visitor): else: assert False - def _iterable_get(self, value, index): + def iterable_get(self, value, index): # Assuming the value is within bounds. if builtins.is_list(value.type): return self.append(ir.GetElem(value, index)) @@ -365,7 +365,7 @@ class ARTIQIRGenerator(algorithm.Visitor): def visit_For(self, node): try: iterable = self.visit(node.iter) - length = self._iterable_len(iterable) + length = self.iterable_len(iterable) prehead = self.current_block head = self.add_block("for.head") @@ -388,7 +388,7 @@ class ARTIQIRGenerator(algorithm.Visitor): body = self.add_block("for.body") self.current_block = body - elt = self._iterable_get(iterable, phi) + elt = self.iterable_get(iterable, phi) try: self.current_assign = elt self.visit(node.target) @@ -669,17 +669,17 @@ class ARTIQIRGenerator(algorithm.Visitor): if isinstance(node.slice, ast.Index): index = self.visit(node.slice.value) - length = self._iterable_len(value, index.type) + length = self.iterable_len(value, index.type) mapped_index = self._map_index(length, index) if self.current_assign is None: - result = self._iterable_get(value, mapped_index) + result = self.iterable_get(value, mapped_index) result.set_name("{}.at.{}".format(value.name, _readable_name(index))) return result else: self.append(ir.SetElem(value, mapped_index, self.current_assign, name="{}.at.{}".format(value.name, _readable_name(index)))) else: # Slice - length = self._iterable_len(value, node.slice.type) + length = self.iterable_len(value, node.slice.type) if node.slice.lower is not None: min_index = self.visit(node.slice.lower) @@ -715,7 +715,7 @@ class ARTIQIRGenerator(algorithm.Visitor): index = self.append(ir.Arith(ast.Add(loc=None), min_index, offset)) if self.current_assign is None: - elem = self._iterable_get(value, index) + 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)) @@ -771,7 +771,7 @@ class ARTIQIRGenerator(algorithm.Visitor): assert comprehension.ifs == [] iterable = self.visit(comprehension.iter) - length = self._iterable_len(iterable) + length = self.iterable_len(iterable) result = self.append(ir.Alloc([length], node.type)) try: @@ -782,7 +782,7 @@ class ARTIQIRGenerator(algorithm.Visitor): self.append(ir.SetLocal(env, ".outer", old_env)) def body_gen(index): - elt = self._iterable_get(iterable, index) + elt = self.iterable_get(iterable, index) try: old_assign, self.current_assign = self.current_assign, elt print(comprehension.target, self.current_assign) @@ -926,7 +926,7 @@ class ARTIQIRGenerator(algorithm.Visitor): else: assert False - def _compare_pair_order(self, op, lhs, rhs): + 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 types.is_tuple(lhs.type) and types.is_tuple(rhs.type): @@ -960,7 +960,7 @@ class ARTIQIRGenerator(algorithm.Visitor): 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._compare_pair(op, lhs_elt, rhs_elt) + body_result = self.polymorphic_compare_pair(op, lhs_elt, rhs_elt) loop_body2 = self.add_block() self.current_block = loop_body2 @@ -989,7 +989,7 @@ class ARTIQIRGenerator(algorithm.Visitor): else: assert False - def _compare_pair_inclusion(self, op, needle, haystack): + def polymorphic_compare_pair_inclusion(self, op, needle, haystack): if builtins.is_range(haystack.type): # Optimized range `in` operator start = self.append(ir.GetAttr(haystack, "start")) @@ -1005,15 +1005,15 @@ class ARTIQIRGenerator(algorithm.Visitor): 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) + elif builtins.isiterable(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._compare_pair(ast.Eq(loc=None), needle, elt) + elt = self.iterable_get(haystack, index) + cmp_result = self.polymorphic_compare_pair(ast.Eq(loc=None), needle, elt) loop_body2 = self.add_block() self.current_block = loop_body2 @@ -1040,7 +1040,7 @@ class ARTIQIRGenerator(algorithm.Visitor): return result - def _compare_pair_identity(self, op, lhs, rhs): + 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)) @@ -1053,15 +1053,15 @@ class ARTIQIRGenerator(algorithm.Visitor): op = ast.NotEq(loc=None) else: assert False - return self._compare_pair_order(op, lhs, rhs) + return self.polymorphic_compare_pair_order(op, lhs, rhs) - def _compare_pair(self, op, lhs, rhs): + def polymorphic_compare_pair(self, op, lhs, rhs): if isinstance(op, (ast.Is, ast.IsNot)): - return self._compare_pair_identity(op, lhs, rhs) + return self.polymorphic_compare_pair_identity(op, lhs, rhs) elif isinstance(op, (ast.In, ast.NotIn)): - return self._compare_pair_inclusion(op, lhs, rhs) + return self.polymorphic_compare_pair_inclusion(op, lhs, rhs) else: # Eq, NotEq, Lt, LtE, Gt, GtE - return self._compare_pair_order(op, lhs, rhs) + return self.polymorphic_compare_pair_order(op, lhs, rhs) def visit_CompareT(self, node): # Essentially a sequence of `and`s performed over results @@ -1070,7 +1070,7 @@ class ARTIQIRGenerator(algorithm.Visitor): lhs = self.visit(node.left) for op, rhs_node in zip(node.ops, node.comparators): rhs = self.visit(rhs_node) - result = self._compare_pair(op, lhs, rhs) + result = self.polymorphic_compare_pair(op, lhs, rhs) blocks.append((result, self.current_block)) self.current_block = self.add_block() lhs = rhs @@ -1120,11 +1120,11 @@ class ARTIQIRGenerator(algorithm.Visitor): return self.append(ir.Alloc(node.type, length)) elif len(node.args) == 1 and len(node.keywords) == 0: arg = self.visit(node.args[0]) - length = self._iterable_len(arg) + 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.iterable_get(arg, index) self.append(ir.SetElem(result, index, elt)) return self.append(ir.Arith(ast.Add(loc=None), index, ir.Constant(1, length.type))) @@ -1136,7 +1136,7 @@ class ARTIQIRGenerator(algorithm.Visitor): else: assert False elif types.is_builtin(typ, "range"): - elt_typ = builtins.get_iterable_elt(node.type) + elt_typ = builtins.getiterable_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([ @@ -1166,7 +1166,7 @@ class ARTIQIRGenerator(algorithm.Visitor): 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) + return self.iterable_len(arg) else: assert False elif types.is_builtin(typ, "round"): @@ -1175,6 +1175,10 @@ class ARTIQIRGenerator(algorithm.Visitor): return self.append(ir.Builtin("round", [arg])) 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_exn_constructor(typ): return self.append(ir.Alloc([self.visit(arg) for args in node.args], node.type)) else: @@ -1206,3 +1210,89 @@ class ARTIQIRGenerator(algorithm.Visitor): invoke = self.append(ir.Invoke(func, args, after_invoke, self.unwind_target)) self.current_block = after_invoke return invoke + + def polymorphic_print(self, values, separator, suffix=""): + format_string = "" + args = [] + def flush(): + nonlocal format_string, args + if format_string != "": + format_arg = [ir.Constant(format_string, builtins.TStr())] + self.append(ir.Builtin("printf", format_arg + args, builtins.TNone())) + 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=", ") + format_string += ")" + elif types.is_function(value.type): + format_string += "" + # We're relying on the internal layout of the closure here. + args.append(self.append(ir.GetAttr(value, 0))) + args.append(self.append(ir.GetAttr(value, 1))) + 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): + format_string += "%d" + args.append(value) + elif builtins.is_float(value.type): + format_string += "%g" + args.append(value) + elif builtins.is_str(value.type): + format_string += "%s" + args.append(value) + elif builtins.is_list(value.type): + format_string += "["; flush() + + 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="") + is_last = self.append(ir.Compare(ast.Lt(loc=None), index, last)) + head = self.current_block + + if_last = self.current_block = self.add_block() + self.append(ir.Builtin("printf", + [ir.Constant(", ", builtins.TStr())], builtins.TNone())) + + tail = self.current_block = self.add_block() + 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) + + 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=", ") + + format_string += ")" + elif builtins.is_exception(value.type): + # TODO: print exceptions + assert False + else: + assert False + + format_string += suffix + flush() diff --git a/artiq/compiler/transforms/inferencer.py b/artiq/compiler/transforms/inferencer.py index 3dd95aee0..a8b87491a 100644 --- a/artiq/compiler/transforms/inferencer.py +++ b/artiq/compiler/transforms/inferencer.py @@ -637,6 +637,19 @@ class Inferencer(algorithm.Visitor): arg.loc, None) 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()) # TODO: add when it is clear what interface syscall() has # elif types.is_builtin(typ, "syscall"): # valid_Forms = lambda: [ diff --git a/artiq/compiler/transforms/llvm_ir_generator.py b/artiq/compiler/transforms/llvm_ir_generator.py index 8405c147c..80908e10b 100644 --- a/artiq/compiler/transforms/llvm_ir_generator.py +++ b/artiq/compiler/transforms/llvm_ir_generator.py @@ -90,6 +90,25 @@ class LLVMIRGenerator: else: assert False + def llbuiltin(self, name): + llfun = self.llmodule.get_global(name) + if llfun is not None: + return llfun + + if name in ("llvm.abort", "llvm.donothing"): + llty = ll.FunctionType(ll.VoidType(), []) + 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 == "printf": + llty = ll.FunctionType(ll.VoidType(), [ll.IntType(8).as_pointer()], var_arg=True) + else: + assert False + return ll.Function(self.llmodule, llty, name) + def map(self, value): if isinstance(value, (ir.Argument, ir.Instruction, ir.BasicBlock)): return self.llmap[value] @@ -214,7 +233,7 @@ class LLVMIRGenerator: def process_GetAttr(self, insn): if types.is_tuple(insn.object().type): - return self.llbuilder.extract_value(self.map(insn.object()), self.attr_index(insn), + return self.llbuilder.extract_value(self.map(insn.object()), insn.attr, name=insn.name) elif not builtins.is_allocated(insn.object().type): return self.llbuilder.extract_value(self.map(insn.object()), self.attr_index(insn), @@ -296,9 +315,7 @@ 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())) - llfnty = ll.FunctionType(ll.DoubleType(), [ll.DoubleType()]) - llfn = ll.Function(self.llmodule, llfnty, "llvm.round.f64") - return self.llbuilder.call(llfn, [llvalue], + return self.llbuilder.call(self.llbuiltin("llvm.round.f64"), [llvalue], name=insn.name) else: return self.llbuilder.sdiv(self.map(insn.lhs()), self.map(insn.rhs()), @@ -312,15 +329,13 @@ class LLVMIRGenerator: name=insn.name) elif isinstance(insn.op, ast.Pow): if builtins.is_float(insn.type): - llfnty = ll.FunctionType(ll.DoubleType(), [ll.DoubleType(), ll.DoubleType()]) - llfn = ll.Function(self.llmodule, llfnty, "llvm.pow.f64") - return self.llbuilder.call(llfn, [self.map(insn.lhs()), self.map(insn.rhs())], + return self.llbuilder.call(self.llbuiltin("llvm.pow.f64"), + [self.map(insn.lhs()), self.map(insn.rhs())], name=insn.name) else: llrhs = self.llbuilder.trunc(self.map(insn.rhs()), ll.IntType(32)) - llfnty = ll.FunctionType(ll.DoubleType(), [ll.DoubleType(), ll.IntType(32)]) - llfn = ll.Function(self.llmodule, llfnty, "llvm.powi.f64") - llvalue = self.llbuilder.call(llfn, [self.map(insn.lhs()), llrhs]) + llvalue = self.llbuilder.call(self.llbuiltin("llvm.powi.f64"), + [self.map(insn.lhs()), llrhs]) return self.llbuilder.fptosi(llvalue, self.llty_of_type(insn.type), name=insn.name) elif isinstance(insn.op, ast.LShift): @@ -366,8 +381,7 @@ class LLVMIRGenerator: def process_Builtin(self, insn): if insn.op == "nop": - fn = ll.Function(self.llmodule, ll.FunctionType(ll.VoidType(), []), "llvm.donothing") - return self.llbuilder.call(fn, []) + return self.llbuilder.call(self.llbuiltin("llvm.donothing"), []) elif insn.op == "unwrap": optarg, default = map(self.map, insn.operands) has_arg = self.llbuilder.extract_value(optarg, 0) @@ -375,9 +389,7 @@ class LLVMIRGenerator: return self.llbuilder.select(has_arg, arg, default, name=insn.name) elif insn.op == "round": - llfnty = ll.FunctionType(ll.DoubleType(), [ll.DoubleType()]) - llfn = ll.Function(self.llmodule, llfnty, "llvm.round.f64") - return self.llbuilder.call(llfn, [llvalue], + return self.llbuilder.call(self.llbuiltin("llvm.round.f64"), [llvalue], name=insn.name) elif insn.op == "globalenv": def get_outer(llenv, env_ty): @@ -394,6 +406,11 @@ class LLVMIRGenerator: elif insn.op == "len": lst, = insn.operands return self.llbuilder.extract_value(self.map(lst), 0) + elif insn.op == "printf": + # We only get integers, floats, pointers and strings here. + llargs = map(self.map, insn.operands) + return self.llbuilder.call(self.llbuiltin("printf"), llargs, + name=insn.name) # elif insn.op == "exncast": else: assert False @@ -414,8 +431,8 @@ class LLVMIRGenerator: name=insn.name) def process_Select(self, insn): - return self.llbuilder.select(self.map(insn.cond()), - self.map(insn.lhs()), self.map(insn.rhs())) + 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())) @@ -438,9 +455,7 @@ class LLVMIRGenerator: def process_Raise(self, insn): # TODO: hack before EH is working - llfnty = ll.FunctionType(ll.VoidType(), []) - llfn = ll.Function(self.llmodule, llfnty, "llvm.abort") - llinsn = self.llbuilder.call(llfn, [], + llinsn = self.llbuilder.call(self.llbuiltin("llvm.abort"), [], name=insn.name) self.llbuilder.unreachable() return llinsn From e21829ce741da106f8cc460bfc1b59f1e531c4b1 Mon Sep 17 00:00:00 2001 From: whitequark Date: Tue, 21 Jul 2015 23:39:22 +0300 Subject: [PATCH 129/369] Require boolean condition in If, While, IfExp. --- artiq/compiler/transforms/inferencer.py | 9 +++++++++ lit-test/compiler/inferencer/error_unify.py | 9 +++++++++ lit-test/compiler/inferencer/gcd.py | 2 +- lit-test/compiler/inferencer/unify.py | 4 ++-- 4 files changed, 21 insertions(+), 3 deletions(-) diff --git a/artiq/compiler/transforms/inferencer.py b/artiq/compiler/transforms/inferencer.py index a8b87491a..3fe2972b2 100644 --- a/artiq/compiler/transforms/inferencer.py +++ b/artiq/compiler/transforms/inferencer.py @@ -145,6 +145,8 @@ class Inferencer(algorithm.Visitor): 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, @@ -788,6 +790,11 @@ class Inferencer(algorithm.Visitor): node.value = self._coerce_one(value_type, node.value, other_node=node.target) + def visit_If(self, node): + self.generic_visit(node) + self._unify(node.test.type, builtins.TBool(), + node.test.loc, None) + def visit_For(self, node): old_in_loop, self.in_loop = self.in_loop, True self.generic_visit(node) @@ -798,6 +805,8 @@ class Inferencer(algorithm.Visitor): old_in_loop, self.in_loop = self.in_loop, True self.generic_visit(node) self.in_loop = old_in_loop + self._unify(node.test.type, builtins.TBool(), + node.test.loc, None) def visit_Break(self, node): if not self.in_loop: diff --git a/lit-test/compiler/inferencer/error_unify.py b/lit-test/compiler/inferencer/error_unify.py index e81537d29..dd5c617a8 100644 --- a/lit-test/compiler/inferencer/error_unify.py +++ b/lit-test/compiler/inferencer/error_unify.py @@ -25,3 +25,12 @@ a = b # CHECK-L: ${LINE:+1}: error: type int(width='a) does not have an attribute 'x' (1).x + +# CHECK-L: ${LINE:+1}: error: cannot unify int(width='a) with bool +1 if 1 else 1 + +# CHECK-L: ${LINE:+1}: error: cannot unify int(width='a) with bool +if 1: pass + +# CHECK-L: ${LINE:+1}: error: cannot unify int(width='a) with bool +while 1: pass diff --git a/lit-test/compiler/inferencer/gcd.py b/lit-test/compiler/inferencer/gcd.py index e2d4b4779..87bd42716 100644 --- a/lit-test/compiler/inferencer/gcd.py +++ b/lit-test/compiler/inferencer/gcd.py @@ -3,7 +3,7 @@ def _gcd(a, b): if a < 0: a = -a - while a: + while a > 0: c = a a = b % a b = c diff --git a/lit-test/compiler/inferencer/unify.py b/lit-test/compiler/inferencer/unify.py index 48681fdcb..59abd4262 100644 --- a/lit-test/compiler/inferencer/unify.py +++ b/lit-test/compiler/inferencer/unify.py @@ -33,8 +33,8 @@ j = [] j += [1.0] # CHECK-L: j:list(elt=float) -1 if a else 2 -# CHECK-L: 1:int(width='f) if a:int(width='a) else 2:int(width='f):int(width='f) +1 if c else 2 +# CHECK-L: 1:int(width='f) if c:bool else 2:int(width='f):int(width='f) True and False # CHECK-L: True:bool and False:bool:bool From 5d518dcec65a891ba324fe4c5c243c35ecb5afca Mon Sep 17 00:00:00 2001 From: whitequark Date: Tue, 21 Jul 2015 23:45:17 +0300 Subject: [PATCH 130/369] Require boolean operand in BoolOp. --- artiq/compiler/transforms/asttyped_rewriter.py | 2 +- artiq/compiler/transforms/inferencer.py | 4 ++-- lit-test/compiler/inferencer/error_unify.py | 2 -- lit-test/compiler/inferencer/unify.py | 13 +++++-------- 4 files changed, 8 insertions(+), 13 deletions(-) diff --git a/artiq/compiler/transforms/asttyped_rewriter.py b/artiq/compiler/transforms/asttyped_rewriter.py index df0b46d91..a1d0b7bd6 100644 --- a/artiq/compiler/transforms/asttyped_rewriter.py +++ b/artiq/compiler/transforms/asttyped_rewriter.py @@ -305,7 +305,7 @@ class ASTTypedRewriter(algorithm.Transformer): def visit_BoolOp(self, node): node = self.generic_visit(node) - node = asttyped.BoolOpT(type=types.TVar(), + node = asttyped.BoolOpT(type=builtins.TBool(), op=node.op, values=node.values, op_locs=node.op_locs, loc=node.loc) return self.visit(node) diff --git a/artiq/compiler/transforms/inferencer.py b/artiq/compiler/transforms/inferencer.py index 3fe2972b2..a821059a9 100644 --- a/artiq/compiler/transforms/inferencer.py +++ b/artiq/compiler/transforms/inferencer.py @@ -155,8 +155,8 @@ class Inferencer(algorithm.Visitor): 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")) + self._unify(value.type, builtins.TBool(), + value.loc, None) def visit_UnaryOpT(self, node): self.generic_visit(node) diff --git a/lit-test/compiler/inferencer/error_unify.py b/lit-test/compiler/inferencer/error_unify.py index dd5c617a8..374c2603c 100644 --- a/lit-test/compiler/inferencer/error_unify.py +++ b/lit-test/compiler/inferencer/error_unify.py @@ -14,8 +14,6 @@ a = b # CHECK-L: ${LINE:+1}: error: cannot unify int(width='a) with bool 1 and False -# CHECK-L: note: an operand of type int(width='a) -# 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) +[] diff --git a/lit-test/compiler/inferencer/unify.py b/lit-test/compiler/inferencer/unify.py index 59abd4262..85db30cb4 100644 --- a/lit-test/compiler/inferencer/unify.py +++ b/lit-test/compiler/inferencer/unify.py @@ -39,20 +39,17 @@ j += [1.0] True and False # CHECK-L: True:bool and False:bool:bool -1 and 0 -# CHECK-L: 1:int(width='g) and 0:int(width='g):int(width='g) - ~1 -# CHECK-L: 1:int(width='h):int(width='h) +# CHECK-L: ~1:int(width='g):int(width='g) -not 1 -# CHECK-L: 1:int(width='i):bool +not True +# CHECK-L: not True:bool:bool [x for x in [1]] -# CHECK-L: [x:int(width='j) for x:int(width='j) in [1:int(width='j)]:list(elt=int(width='j))]:list(elt=int(width='j)) +# CHECK-L: [x:int(width='h) for x:int(width='h) in [1:int(width='h)]:list(elt=int(width='h))]:list(elt=int(width='h)) lambda x, y=1: x -# CHECK-L: lambda x:'k, y:int(width='l)=1:int(width='l): x:'k:(x:'k, ?y:int(width='l))->'k +# CHECK-L: lambda x:'i, y:int(width='j)=1:int(width='j): x:'i:(x:'i, ?y:int(width='j))->'i k = "x" # CHECK-L: k:str From 236d5b886a4510a576803936a878985a36c7f9db Mon Sep 17 00:00:00 2001 From: whitequark Date: Wed, 22 Jul 2015 02:58:59 +0300 Subject: [PATCH 131/369] Add support for Assert. --- artiq/compiler/ir.py | 33 ++++- .../compiler/transforms/artiq_ir_generator.py | 118 ++++++++++++++++-- .../compiler/transforms/asttyped_rewriter.py | 1 - .../transforms/dead_code_eliminator.py | 23 +++- artiq/compiler/transforms/inferencer.py | 11 ++ .../compiler/transforms/llvm_ir_generator.py | 28 ++++- lit-test/compiler/inferencer/error_assert.py | 6 + 7 files changed, 199 insertions(+), 21 deletions(-) create mode 100644 lit-test/compiler/inferencer/error_assert.py diff --git a/artiq/compiler/ir.py b/artiq/compiler/ir.py index 55313b7ed..f94df0ed4 100644 --- a/artiq/compiler/ir.py +++ b/artiq/compiler/ir.py @@ -106,14 +106,13 @@ class User(NamedValue): def __init__(self, operands, typ, name): super().__init__(typ, name) self.operands = [] - if operands is not None: - self.set_operands(operands) + self.set_operands(operands) def set_operands(self, new_operands): - for operand in self.operands: + for operand in set(self.operands): operand.uses.remove(self) self.operands = new_operands - for operand in self.operands: + for operand in set(self.operands): operand.uses.add(self) def drop_references(self): @@ -162,6 +161,9 @@ class Instruction(User): 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) @@ -220,7 +222,21 @@ class Phi(Instruction): 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) + 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) + self.operands[index - 1].uses.remove(self) + self.operands[index].uses.remove(self) + del self.operands[index - 1:index + 1] def __str__(self): if builtins.is_none(self.type): @@ -268,9 +284,13 @@ class BasicBlock(NamedValue): self.function.remove(self) def erase(self): - for insn in self.instructions: + # self.instructions is updated while iterating + for insn in list(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) @@ -817,6 +837,7 @@ class Select(Instruction): """ 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 @@ -864,8 +885,10 @@ class BranchIf(Terminator): """ 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): diff --git a/artiq/compiler/transforms/artiq_ir_generator.py b/artiq/compiler/transforms/artiq_ir_generator.py index 715845b45..c7e633295 100644 --- a/artiq/compiler/transforms/artiq_ir_generator.py +++ b/artiq/compiler/transforms/artiq_ir_generator.py @@ -39,16 +39,22 @@ class ARTIQIRGenerator(algorithm.Visitor): 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.Environment`) + :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.Environment`) + :ivar current_private_env: (:class:`ir.Alloc` of type :class:`ir.TEnvironment`) the private function environment, containing internal state :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 current_assert_env: (:class:`ir.Alloc` of type :class:`ir.TEnvironment`) + the environment where the individual components of current assert + statement are stored until display + :ivar current_assert_subexprs: (list of (:class:`ast.AST`, string)) + the mapping from components of current assert statement to the names + their values have in :ivar:`current_assert_env` :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) @@ -72,6 +78,8 @@ class ARTIQIRGenerator(algorithm.Visitor): self.current_env = None self.current_private_env = None self.current_assign = None + self.current_assert_env = None + self.current_assert_subexprs = None self.break_target = None self.continue_target = None self.return_target = None @@ -203,7 +211,7 @@ class ARTIQIRGenerator(algorithm.Visitor): self.append(ir.SetLocal(env, arg_name, args[index])) for index, (arg_name, env_default_name) in enumerate(zip(typ.optargs, defaults)): default = self.append(ir.GetLocal(self.current_env, env_default_name)) - value = self.append(ir.Builtin("unwrap", [optargs[index], default], + value = self.append(ir.Builtin("unwrap_or", [optargs[index], default], typ.optargs[arg_name])) self.append(ir.SetLocal(env, arg_name, value)) @@ -736,7 +744,7 @@ class ARTIQIRGenerator(algorithm.Visitor): for index, elt_node in enumerate(node.elts): self.current_assign = \ self.append(ir.GetAttr(old_assign, index, - name="{}.{}".format(old_assign.name, index)), + name="{}.e{}".format(old_assign.name, index)), loc=elt_node.loc) self.visit(elt_node) finally: @@ -805,18 +813,26 @@ class ARTIQIRGenerator(algorithm.Visitor): def visit_BoolOpT(self, node): blocks = [] for value_node in node.values: + value_head = self.current_block value = self.visit(value_node) - blocks.append((value, self.current_block)) + self.instrument_assert(value_node, value) + value_tail = self.current_block + + blocks.append((value, value_head, value_tail)) self.current_block = self.add_block() 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 isinstance(node.op, ast.And): - block.append(ir.BranchIf(value, next_block, tail)) + 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: + if isinstance(node.op, ast.And): + value_tail.append(ir.BranchIf(value, next_value_head, tail)) + else: + value_tail.append(ir.BranchIf(value, tail, next_value_head)) else: - block.append(ir.BranchIf(value, tail, next_block)) + value_tail.append(ir.Branch(tail)) return phi def visit_UnaryOpT(self, node): @@ -1005,7 +1021,7 @@ class ARTIQIRGenerator(algorithm.Visitor): ir.Constant(False, builtins.TBool()))) result = self.append(ir.Select(result, on_step, ir.Constant(False, builtins.TBool()))) - elif builtins.isiterable(haystack.type): + elif builtins.is_iterable(haystack.type): length = self.iterable_len(haystack) cmp_result = loop_body2 = None @@ -1068,8 +1084,10 @@ class ARTIQIRGenerator(algorithm.Visitor): # of comparisons. blocks = [] lhs = self.visit(node.left) + self.instrument_assert(node.left, lhs) for op, rhs_node in zip(node.ops, node.comparators): 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)) self.current_block = self.add_block() @@ -1079,7 +1097,10 @@ class ARTIQIRGenerator(algorithm.Visitor): 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) - block.append(ir.BranchIf(value, next_block, tail)) + if next_block != tail: + block.append(ir.BranchIf(value, next_block, tail)) + else: + block.append(ir.Branch(tail)) return phi def visit_builtin_call(self, node): @@ -1211,6 +1232,79 @@ class ARTIQIRGenerator(algorithm.Visitor): self.current_block = after_invoke return invoke + def instrument_assert(self, node, value): + if self.current_assert_env is not None: + if isinstance(value, ir.Constant): + return # don't display the values of constants + + if any([algorithm.compare(node, subexpr) + 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)) + 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), + loc=node.loc) + self.current_assert_subexprs.append((node, name)) + + def visit_Assert(self, node): + try: + assert_env = self.current_assert_env = \ + self.append(ir.Alloc([], ir.TEnvironment({}), name="assertenv")) + assert_subexprs = self.current_assert_subexprs = [] + init = self.current_block + + prehead = self.current_block = self.add_block() + cond = self.visit(node.test) + head = self.current_block + finally: + self.current_assert_env = None + self.current_assert_subexprs = None + + for subexpr_node, subexpr_name in assert_subexprs: + empty = init.append(ir.Alloc([], ir.TOption(subexpr_node.type))) + init.append(ir.SetLocal(assert_env, subexpr_name, empty)) + init.append(ir.Branch(prehead)) + + if_failed = self.current_block = self.add_block() + + if node.msg: + explanation = node.msg.s + else: + explanation = node.loc.source() + self.append(ir.Builtin("printf", [ + ir.Constant("assertion failed at %s: %s\n", builtins.TStr()), + ir.Constant(str(node.loc.begin()), builtins.TStr()), + ir.Constant(str(explanation), builtins.TStr()), + ], builtins.TNone())) + + for subexpr_node, subexpr_name in assert_subexprs: + subexpr_head = self.current_block + subexpr_value_opt = self.append(ir.GetLocal(assert_env, subexpr_name)) + subexpr_cond = self.append(ir.Builtin("is_some", [subexpr_value_opt], + builtins.TBool())) + + subexpr_body = self.current_block = self.add_block() + self.append(ir.Builtin("printf", [ + ir.Constant(" (%s) = ", builtins.TStr()), + ir.Constant(subexpr_node.loc.source(), builtins.TStr()) + ], builtins.TNone())) + subexpr_value = self.append(ir.Builtin("unwrap", [subexpr_value_opt], + subexpr_node.type)) + self.polymorphic_print([subexpr_value], separator="", suffix="\n") + subexpr_postbody = self.current_block + + subexpr_tail = self.current_block = self.add_block() + self.append(ir.Branch(subexpr_tail), block=subexpr_postbody) + self.append(ir.BranchIf(subexpr_cond, subexpr_body, subexpr_tail), block=subexpr_head) + + self.append(ir.Builtin("abort", [], builtins.TNone())) + self.append(ir.Unreachable()) + + tail = self.current_block = self.add_block() + self.append(ir.BranchIf(cond, tail, if_failed), block=head) + def polymorphic_print(self, values, separator, suffix=""): format_string = "" args = [] diff --git a/artiq/compiler/transforms/asttyped_rewriter.py b/artiq/compiler/transforms/asttyped_rewriter.py index a1d0b7bd6..2b8d03f73 100644 --- a/artiq/compiler/transforms/asttyped_rewriter.py +++ b/artiq/compiler/transforms/asttyped_rewriter.py @@ -421,7 +421,6 @@ class ASTTypedRewriter(algorithm.Transformer): visit_YieldFrom = visit_unsupported # stmt - visit_Assert = visit_unsupported visit_ClassDef = visit_unsupported visit_Delete = visit_unsupported visit_Import = visit_unsupported diff --git a/artiq/compiler/transforms/dead_code_eliminator.py b/artiq/compiler/transforms/dead_code_eliminator.py index 92d82b4a7..1d4e08f6f 100644 --- a/artiq/compiler/transforms/dead_code_eliminator.py +++ b/artiq/compiler/transforms/dead_code_eliminator.py @@ -16,4 +16,25 @@ class DeadCodeEliminator: def process_function(self, func): for block in func.basic_blocks: if not any(block.predecessors()) and block != func.entry(): - block.erase() + self.remove_block(block) + + 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) + 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) + + insn.erase() diff --git a/artiq/compiler/transforms/inferencer.py b/artiq/compiler/transforms/inferencer.py index a821059a9..21e803e9b 100644 --- a/artiq/compiler/transforms/inferencer.py +++ b/artiq/compiler/transforms/inferencer.py @@ -947,3 +947,14 @@ class Inferencer(algorithm.Visitor): else: self._unify(self.function.return_type, node.value.type, self.function.name_loc, node.value.loc, makenotes) + + 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/llvm_ir_generator.py b/artiq/compiler/transforms/llvm_ir_generator.py index 80908e10b..e73505d7b 100644 --- a/artiq/compiler/transforms/llvm_ir_generator.py +++ b/artiq/compiler/transforms/llvm_ir_generator.py @@ -95,7 +95,9 @@ class LLVMIRGenerator: if llfun is not None: return llfun - if name in ("llvm.abort", "llvm.donothing"): + if name in "llvm.donothing": + llty = ll.FunctionType(ll.VoidType(), []) + elif name in "llvm.trap": llty = ll.FunctionType(ll.VoidType(), []) elif name == "llvm.round.f64": llty = ll.FunctionType(ll.DoubleType(), [ll.DoubleType()]) @@ -181,6 +183,18 @@ class LLVMIRGenerator: 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(ll.IntType(1), 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(ll.IntType(1), True), 0) + return self.llbuilder.insert_value(llvalue, self.map(insn.operands[0]), 1, + name=insn.name) + else: + assert False elif builtins.is_list(insn.type): llsize = self.map(insn.operands[0]) llvalue = ll.Constant(self.llty_of_type(insn.type), ll.Undefined) @@ -382,7 +396,17 @@ class LLVMIRGenerator: def process_Builtin(self, insn): if insn.op == "nop": return self.llbuilder.call(self.llbuiltin("llvm.donothing"), []) + 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, + name=insn.name) elif insn.op == "unwrap": + optarg = self.map(insn.operands[0]) + return self.llbuilder.extract_value(optarg, 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) @@ -455,7 +479,7 @@ class LLVMIRGenerator: def process_Raise(self, insn): # TODO: hack before EH is working - llinsn = self.llbuilder.call(self.llbuiltin("llvm.abort"), [], + llinsn = self.llbuilder.call(self.llbuiltin("llvm.trap"), [], name=insn.name) self.llbuilder.unreachable() return llinsn diff --git a/lit-test/compiler/inferencer/error_assert.py b/lit-test/compiler/inferencer/error_assert.py new file mode 100644 index 000000000..1e7c10284 --- /dev/null +++ b/lit-test/compiler/inferencer/error_assert.py @@ -0,0 +1,6 @@ +# 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 From 86e006830cce0e15824501a3c1deab6dd2de01d8 Mon Sep 17 00:00:00 2001 From: whitequark Date: Wed, 22 Jul 2015 03:05:15 +0300 Subject: [PATCH 132/369] Use the correct printf format for 64-bit integers. --- artiq/compiler/transforms/artiq_ir_generator.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/artiq/compiler/transforms/artiq_ir_generator.py b/artiq/compiler/transforms/artiq_ir_generator.py index c7e633295..ab74a2374 100644 --- a/artiq/compiler/transforms/artiq_ir_generator.py +++ b/artiq/compiler/transforms/artiq_ir_generator.py @@ -1339,7 +1339,13 @@ class ARTIQIRGenerator(algorithm.Visitor): ir.Constant("True", builtins.TStr()), ir.Constant("False", builtins.TStr())))) elif builtins.is_int(value.type): - format_string += "%d" + width = builtins.get_int_width(value.type) + if width <= 32: + format_string += "%ld" + elif width <= 64: + format_string += "%lld" + else: + assert False args.append(value) elif builtins.is_float(value.type): format_string += "%g" From 986d9d944f9387d54958e6e73621ecf1fbc63226 Mon Sep 17 00:00:00 2001 From: whitequark Date: Wed, 22 Jul 2015 04:10:15 +0300 Subject: [PATCH 133/369] Add artiq.compiler.testbench.run. --- artiq/compiler/testbench/llvmgen.py | 7 ------- artiq/compiler/testbench/run.py | 32 +++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 7 deletions(-) create mode 100644 artiq/compiler/testbench/run.py diff --git a/artiq/compiler/testbench/llvmgen.py b/artiq/compiler/testbench/llvmgen.py index c32ceb98e..ecb396080 100644 --- a/artiq/compiler/testbench/llvmgen.py +++ b/artiq/compiler/testbench/llvmgen.py @@ -13,13 +13,6 @@ 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/testbench/run.py b/artiq/compiler/testbench/run.py new file mode 100644 index 000000000..3febb9d67 --- /dev/null +++ b/artiq/compiler/testbench/run.py @@ -0,0 +1,32 @@ +import sys, fileinput +from ctypes import CFUNCTYPE +from pythonparser import diagnostic +from llvmlite import binding as llvm +from .. import Module + +llvm.initialize() +llvm.initialize_native_target() +llvm.initialize_native_asmprinter() +llvm.check_jit_execution() + +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 + + llmod = Module.from_string("".join(fileinput.input()).expandtabs(), engine=engine).llvm_ir + + lltarget = llvm.Target.from_default_triple() + llmachine = lltarget.create_target_machine() + llparsedmod = llvm.parse_assembly(str(llmod)) + lljit = llvm.create_mcjit_compiler(llparsedmod, llmachine) + lljit.finalize_object() + llmain = lljit.get_pointer_to_global(llparsedmod.get_function(llmod.name + ".__modinit__")) + CFUNCTYPE(None)(llmain)() + +if __name__ == "__main__": + main() From dff4ce7e3adbd0d5c5bc02619ffe8dee3ad1f42a Mon Sep 17 00:00:00 2001 From: whitequark Date: Wed, 22 Jul 2015 04:13:04 +0300 Subject: [PATCH 134/369] Return LLVM IR module from LLVMIRGenerator.process. --- artiq/compiler/module.py | 3 +-- artiq/compiler/transforms/llvm_ir_generator.py | 2 ++ 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/artiq/compiler/module.py b/artiq/compiler/module.py index e1eb53be6..38bccda92 100644 --- a/artiq/compiler/module.py +++ b/artiq/compiler/module.py @@ -34,8 +34,7 @@ class Module: self.artiq_ir = artiq_ir_generator.visit(self.typedtree) dead_code_eliminator.process(self.artiq_ir) local_access_validator.process(self.artiq_ir) - llvm_ir_generator.process(self.artiq_ir) - self.llvm_ir = llvm_ir_generator.llmodule + self.llvm_ir = llvm_ir_generator.process(self.artiq_ir) @classmethod def from_string(cls, source_string, name="input.py", first_line=1, engine=None): diff --git a/artiq/compiler/transforms/llvm_ir_generator.py b/artiq/compiler/transforms/llvm_ir_generator.py index e73505d7b..cf9deea1d 100644 --- a/artiq/compiler/transforms/llvm_ir_generator.py +++ b/artiq/compiler/transforms/llvm_ir_generator.py @@ -130,6 +130,8 @@ class LLVMIRGenerator: for func in functions: self.process_function(func) + return self.llmodule + def process_function(self, func): try: self.llfunction = self.llmodule.get_global(func.name) From f2a6110cc49d8cf4f30069396be12601f86166d3 Mon Sep 17 00:00:00 2001 From: whitequark Date: Wed, 22 Jul 2015 18:34:52 +0300 Subject: [PATCH 135/369] 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 From 51aef980a09a38009a8bfeec823ef3064e69c433 Mon Sep 17 00:00:00 2001 From: whitequark Date: Wed, 22 Jul 2015 18:35:14 +0300 Subject: [PATCH 136/369] Revert "Require boolean condition in If, While, IfExp." This reverts commit e21829ce741da106f8cc460bfc1b59f1e531c4b1. --- artiq/compiler/transforms/inferencer.py | 9 --------- lit-test/compiler/inferencer/error_unify.py | 9 --------- lit-test/compiler/inferencer/gcd.py | 2 +- lit-test/compiler/inferencer/unify.py | 4 ++-- 4 files changed, 3 insertions(+), 21 deletions(-) diff --git a/artiq/compiler/transforms/inferencer.py b/artiq/compiler/transforms/inferencer.py index f6a4cee7f..649dfb540 100644 --- a/artiq/compiler/transforms/inferencer.py +++ b/artiq/compiler/transforms/inferencer.py @@ -146,8 +146,6 @@ class Inferencer(algorithm.Visitor): 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, @@ -794,11 +792,6 @@ class Inferencer(algorithm.Visitor): node.value = self._coerce_one(value_type, node.value, other_node=node.target) - def visit_If(self, node): - self.generic_visit(node) - self._unify(node.test.type, builtins.TBool(), - node.test.loc, None) - def visit_For(self, node): old_in_loop, self.in_loop = self.in_loop, True self.generic_visit(node) @@ -809,8 +802,6 @@ class Inferencer(algorithm.Visitor): old_in_loop, self.in_loop = self.in_loop, True self.generic_visit(node) self.in_loop = old_in_loop - self._unify(node.test.type, builtins.TBool(), - node.test.loc, None) def visit_Break(self, node): if not self.in_loop: diff --git a/lit-test/compiler/inferencer/error_unify.py b/lit-test/compiler/inferencer/error_unify.py index 374c2603c..e1886b8e8 100644 --- a/lit-test/compiler/inferencer/error_unify.py +++ b/lit-test/compiler/inferencer/error_unify.py @@ -23,12 +23,3 @@ a = b # CHECK-L: ${LINE:+1}: error: type int(width='a) does not have an attribute 'x' (1).x - -# CHECK-L: ${LINE:+1}: error: cannot unify int(width='a) with bool -1 if 1 else 1 - -# CHECK-L: ${LINE:+1}: error: cannot unify int(width='a) with bool -if 1: pass - -# CHECK-L: ${LINE:+1}: error: cannot unify int(width='a) with bool -while 1: pass diff --git a/lit-test/compiler/inferencer/gcd.py b/lit-test/compiler/inferencer/gcd.py index 87bd42716..e2d4b4779 100644 --- a/lit-test/compiler/inferencer/gcd.py +++ b/lit-test/compiler/inferencer/gcd.py @@ -3,7 +3,7 @@ def _gcd(a, b): if a < 0: a = -a - while a > 0: + while a: c = a a = b % a b = c diff --git a/lit-test/compiler/inferencer/unify.py b/lit-test/compiler/inferencer/unify.py index 85db30cb4..72d3fdde9 100644 --- a/lit-test/compiler/inferencer/unify.py +++ b/lit-test/compiler/inferencer/unify.py @@ -33,8 +33,8 @@ j = [] j += [1.0] # CHECK-L: j:list(elt=float) -1 if c else 2 -# CHECK-L: 1:int(width='f) if c:bool else 2:int(width='f):int(width='f) +1 if a else 2 +# CHECK-L: 1:int(width='f) if a:int(width='a) else 2:int(width='f):int(width='f) True and False # CHECK-L: True:bool and False:bool:bool From de181e0cb9e48b012f5df13f50572e30ffbf9cd4 Mon Sep 17 00:00:00 2001 From: whitequark Date: Wed, 22 Jul 2015 18:35:18 +0300 Subject: [PATCH 137/369] Revert "Require boolean operand in BoolOp." This reverts commit 5d518dcec65a891ba324fe4c5c243c35ecb5afca. --- artiq/compiler/transforms/asttyped_rewriter.py | 2 +- artiq/compiler/transforms/inferencer.py | 4 ++-- lit-test/compiler/inferencer/error_unify.py | 2 ++ lit-test/compiler/inferencer/unify.py | 15 +++++++++------ 4 files changed, 14 insertions(+), 9 deletions(-) diff --git a/artiq/compiler/transforms/asttyped_rewriter.py b/artiq/compiler/transforms/asttyped_rewriter.py index 3adfde595..307033d1d 100644 --- a/artiq/compiler/transforms/asttyped_rewriter.py +++ b/artiq/compiler/transforms/asttyped_rewriter.py @@ -305,7 +305,7 @@ class ASTTypedRewriter(algorithm.Transformer): def visit_BoolOp(self, node): node = self.generic_visit(node) - node = asttyped.BoolOpT(type=builtins.TBool(), + node = asttyped.BoolOpT(type=types.TVar(), op=node.op, values=node.values, op_locs=node.op_locs, loc=node.loc) return self.visit(node) diff --git a/artiq/compiler/transforms/inferencer.py b/artiq/compiler/transforms/inferencer.py index 649dfb540..3422abf85 100644 --- a/artiq/compiler/transforms/inferencer.py +++ b/artiq/compiler/transforms/inferencer.py @@ -154,8 +154,8 @@ class Inferencer(algorithm.Visitor): def visit_BoolOpT(self, node): self.generic_visit(node) for value in node.values: - self._unify(value.type, builtins.TBool(), - value.loc, None) + 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) diff --git a/lit-test/compiler/inferencer/error_unify.py b/lit-test/compiler/inferencer/error_unify.py index e1886b8e8..e81537d29 100644 --- a/lit-test/compiler/inferencer/error_unify.py +++ b/lit-test/compiler/inferencer/error_unify.py @@ -14,6 +14,8 @@ a = b # CHECK-L: ${LINE:+1}: error: cannot unify int(width='a) with bool 1 and False +# CHECK-L: note: an operand of type int(width='a) +# 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) +[] diff --git a/lit-test/compiler/inferencer/unify.py b/lit-test/compiler/inferencer/unify.py index 72d3fdde9..48681fdcb 100644 --- a/lit-test/compiler/inferencer/unify.py +++ b/lit-test/compiler/inferencer/unify.py @@ -39,17 +39,20 @@ j += [1.0] True and False # CHECK-L: True:bool and False:bool:bool -~1 -# CHECK-L: ~1:int(width='g):int(width='g) +1 and 0 +# CHECK-L: 1:int(width='g) and 0:int(width='g):int(width='g) -not True -# CHECK-L: not True:bool:bool +~1 +# CHECK-L: 1:int(width='h):int(width='h) + +not 1 +# CHECK-L: 1:int(width='i):bool [x for x in [1]] -# CHECK-L: [x:int(width='h) for x:int(width='h) in [1:int(width='h)]:list(elt=int(width='h))]:list(elt=int(width='h)) +# CHECK-L: [x:int(width='j) for x:int(width='j) in [1:int(width='j)]:list(elt=int(width='j))]:list(elt=int(width='j)) lambda x, y=1: x -# CHECK-L: lambda x:'i, y:int(width='j)=1:int(width='j): x:'i:(x:'i, ?y:int(width='j))->'i +# CHECK-L: lambda x:'k, y:int(width='l)=1:int(width='l): x:'k:(x:'k, ?y:int(width='l))->'k k = "x" # CHECK-L: k:str From bf60978c7be5397e70ab29c694371dfe6fa12f6f Mon Sep 17 00:00:00 2001 From: whitequark Date: Wed, 22 Jul 2015 19:09:14 +0300 Subject: [PATCH 138/369] Add bool coercion support. --- .../compiler/transforms/artiq_ir_generator.py | 37 ++++++++++++++++--- .../compiler/transforms/llvm_ir_generator.py | 2 +- .../compiler/codegen/warning_useless_bool.py | 5 +++ .../{operator.py => arithmetics.py} | 2 - lit-test/compiler/integration/bool.py | 20 ++++++++++ 5 files changed, 57 insertions(+), 9 deletions(-) create mode 100644 lit-test/compiler/codegen/warning_useless_bool.py rename lit-test/compiler/integration/{operator.py => arithmetics.py} (93%) create mode 100644 lit-test/compiler/integration/bool.py diff --git a/artiq/compiler/transforms/artiq_ir_generator.py b/artiq/compiler/transforms/artiq_ir_generator.py index 09cea92ad..0d17417d9 100644 --- a/artiq/compiler/transforms/artiq_ir_generator.py +++ b/artiq/compiler/transforms/artiq_ir_generator.py @@ -284,8 +284,33 @@ class ARTIQIRGenerator(algorithm.Visitor): 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) + 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() @@ -838,17 +863,19 @@ class ARTIQIRGenerator(algorithm.Visitor): 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(value, next_value_head, tail)) + value_tail.append(ir.BranchIf(cond, next_value_head, tail)) else: - value_tail.append(ir.BranchIf(value, tail, next_value_head)) + value_tail.append(ir.BranchIf(cond, tail, next_value_head)) else: value_tail.append(ir.Branch(tail)) return phi def visit_UnaryOpT(self, node): if isinstance(node.op, ast.Not): - return self.append(ir.Select(self.visit(node.operand), + 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.USub): @@ -1116,9 +1143,7 @@ class ARTIQIRGenerator(algorithm.Visitor): return ir.Constant(False, builtins.TBool()) elif len(node.args) == 1 and len(node.keywords) == 0: arg = self.visit(node.args[0]) - return self.append(ir.Select(arg, - ir.Constant(True, builtins.TBool()), - ir.Constant(False, builtins.TBool()))) + return self.coerce_to_bool(arg) else: assert False elif types.is_builtin(typ, "int"): diff --git a/artiq/compiler/transforms/llvm_ir_generator.py b/artiq/compiler/transforms/llvm_ir_generator.py index 61b29abee..5c9c78fe4 100644 --- a/artiq/compiler/transforms/llvm_ir_generator.py +++ b/artiq/compiler/transforms/llvm_ir_generator.py @@ -295,7 +295,7 @@ class LLVMIRGenerator: 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): + 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: diff --git a/lit-test/compiler/codegen/warning_useless_bool.py b/lit-test/compiler/codegen/warning_useless_bool.py new file mode 100644 index 000000000..f76e011c8 --- /dev/null +++ b/lit-test/compiler/codegen/warning_useless_bool.py @@ -0,0 +1,5 @@ +# RUN: %python -m artiq.compiler.testbench.module +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/lit-test/compiler/integration/operator.py b/lit-test/compiler/integration/arithmetics.py similarity index 93% rename from lit-test/compiler/integration/operator.py rename to lit-test/compiler/integration/arithmetics.py index 4bf0ab7c1..a93dd5945 100644 --- a/lit-test/compiler/integration/operator.py +++ b/lit-test/compiler/integration/arithmetics.py @@ -1,7 +1,5 @@ # 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 diff --git a/lit-test/compiler/integration/bool.py b/lit-test/compiler/integration/bool.py new file mode 100644 index 000000000..9df8b97c2 --- /dev/null +++ b/lit-test/compiler/integration/bool.py @@ -0,0 +1,20 @@ +# RUN: %python -m artiq.compiler.testbench.jit %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 From 4cfe4ea14816896146e0a6ade6f82a155ad1a83c Mon Sep 17 00:00:00 2001 From: whitequark Date: Thu, 23 Jul 2015 00:58:41 +0300 Subject: [PATCH 139/369] Make negative and too-far shifts have defined behavior. --- .../compiler/transforms/artiq_ir_generator.py | 10 ++++++++-- artiq/compiler/transforms/llvm_ir_generator.py | 18 ++++++++++++++---- lit-test/compiler/integration/arithmetics.py | 2 ++ 3 files changed, 24 insertions(+), 6 deletions(-) diff --git a/artiq/compiler/transforms/artiq_ir_generator.py b/artiq/compiler/transforms/artiq_ir_generator.py index 0d17417d9..502ad651a 100644 --- a/artiq/compiler/transforms/artiq_ir_generator.py +++ b/artiq/compiler/transforms/artiq_ir_generator.py @@ -900,8 +900,14 @@ class ARTIQIRGenerator(algorithm.Visitor): def visit_BinOpT(self, node): if builtins.is_numeric(node.type): # TODO: check for division by zero - # TODO: check for shift by too many bits - return self.append(ir.Arith(node.op, self.visit(node.left), self.visit(node.right))) + 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.append(ir.Alloc([], builtins.TValueError()))) + + return self.append(ir.Arith(node.op, self.visit(node.left), rhs)) 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 types.is_tuple(node.right.type): diff --git a/artiq/compiler/transforms/llvm_ir_generator.py b/artiq/compiler/transforms/llvm_ir_generator.py index 5c9c78fe4..5d4075f8b 100644 --- a/artiq/compiler/transforms/llvm_ir_generator.py +++ b/artiq/compiler/transforms/llvm_ir_generator.py @@ -368,11 +368,21 @@ class LLVMIRGenerator: return self.llbuilder.fptosi(llvalue, self.llty_of_type(insn.type), name=insn.name) elif isinstance(insn.op, ast.LShift): - return self.llbuilder.shl(self.map(insn.lhs()), self.map(insn.rhs()), - name=insn.name) + 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): - return self.llbuilder.ashr(self.map(insn.lhs()), self.map(insn.rhs()), - name=insn.name) + 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) diff --git a/lit-test/compiler/integration/arithmetics.py b/lit-test/compiler/integration/arithmetics.py index a93dd5945..8a278aff6 100644 --- a/lit-test/compiler/integration/arithmetics.py +++ b/lit-test/compiler/integration/arithmetics.py @@ -28,6 +28,8 @@ assert 9.0 ** 0.5 == 3.0 assert 1 << 1 == 2 assert 2 >> 1 == 1 assert -2 >> 1 == -1 +assert 1 << 32 == 0 +assert -1 >> 32 == -1 assert 0x18 & 0x0f == 0x08 assert 0x18 | 0x0f == 0x1f assert 0x18 ^ 0x0f == 0x17 From f8c2709943587caf90d9d023b47a9e948646ed15 Mon Sep 17 00:00:00 2001 From: whitequark Date: Thu, 23 Jul 2015 01:26:50 +0300 Subject: [PATCH 140/369] Make division by zero raise an exception. --- artiq/compiler/builtins.py | 7 ++++++ artiq/compiler/prelude.py | 25 ++++++++++--------- .../compiler/transforms/artiq_ir_generator.py | 5 +++- 3 files changed, 24 insertions(+), 13 deletions(-) diff --git a/artiq/compiler/builtins.py b/artiq/compiler/builtins.py index dde80af12..73035600d 100644 --- a/artiq/compiler/builtins.py +++ b/artiq/compiler/builtins.py @@ -83,6 +83,10 @@ class TValueError(TException): def __init__(self): super().__init__("ValueError") +class TZeroDivisionError(TException): + def __init__(self): + super().__init__("ZeroDivisionError") + def fn_bool(): return types.TConstructor("bool") @@ -107,6 +111,9 @@ def fn_IndexError(): def fn_ValueError(): return types.TExceptionConstructor("ValueError") +def fn_ZeroDivisionError(): + return types.TExceptionConstructor("ZeroDivisionError") + def fn_range(): return types.TBuiltinFunction("range") diff --git a/artiq/compiler/prelude.py b/artiq/compiler/prelude.py index bed44af6c..600b7a800 100644 --- a/artiq/compiler/prelude.py +++ b/artiq/compiler/prelude.py @@ -7,16 +7,17 @@ from . import builtins def globals(): return { - "bool": builtins.fn_bool(), - "int": builtins.fn_int(), - "float": builtins.fn_float(), - "list": builtins.fn_list(), - "range": builtins.fn_range(), - "Exception": builtins.fn_Exception(), - "IndexError": builtins.fn_IndexError(), - "ValueError": builtins.fn_ValueError(), - "len": builtins.fn_len(), - "round": builtins.fn_round(), - "print": builtins.fn_print(), - "syscall": builtins.fn_syscall(), + "bool": builtins.fn_bool(), + "int": builtins.fn_int(), + "float": builtins.fn_float(), + "list": builtins.fn_list(), + "range": builtins.fn_range(), + "Exception": builtins.fn_Exception(), + "IndexError": builtins.fn_IndexError(), + "ValueError": builtins.fn_ValueError(), + "ZeroDivisionError": builtins.fn_ZeroDivisionError(), + "len": builtins.fn_len(), + "round": builtins.fn_round(), + "print": builtins.fn_print(), + "syscall": builtins.fn_syscall(), } diff --git a/artiq/compiler/transforms/artiq_ir_generator.py b/artiq/compiler/transforms/artiq_ir_generator.py index 502ad651a..1c49508e2 100644 --- a/artiq/compiler/transforms/artiq_ir_generator.py +++ b/artiq/compiler/transforms/artiq_ir_generator.py @@ -899,13 +899,16 @@ class ARTIQIRGenerator(algorithm.Visitor): def visit_BinOpT(self, node): if builtins.is_numeric(node.type): - # TODO: check for division by zero 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.append(ir.Alloc([], builtins.TValueError()))) + elif isinstance(node.op, (ast.Div, ast.FloorDiv)): + self._make_check(self.append(ir.Compare(ast.NotEq(loc=None), rhs, + ir.Constant(0, rhs.type))), + lambda: self.append(ir.Alloc([], builtins.TZeroDivisionError()))) return self.append(ir.Arith(node.op, self.visit(node.left), rhs)) elif isinstance(node.op, ast.Add): # list + list, tuple + tuple From acb8810e628602b8cf23b6bf704e836d93f4b0fe Mon Sep 17 00:00:00 2001 From: whitequark Date: Thu, 23 Jul 2015 03:07:30 +0300 Subject: [PATCH 141/369] Add tests for lambdas and functions. --- artiq/compiler/module.py | 2 + .../compiler/transforms/artiq_ir_generator.py | 41 ++++++++++++++----- .../compiler/transforms/llvm_ir_generator.py | 2 +- lit-test/compiler/integration/function.py | 10 +++++ lit-test/compiler/integration/lambda.py | 9 ++++ 5 files changed, 52 insertions(+), 12 deletions(-) create mode 100644 lit-test/compiler/integration/function.py create mode 100644 lit-test/compiler/integration/lambda.py diff --git a/artiq/compiler/module.py b/artiq/compiler/module.py index 38bccda92..80e44acd3 100644 --- a/artiq/compiler/module.py +++ b/artiq/compiler/module.py @@ -32,6 +32,8 @@ class Module: monomorphism_validator.visit(self.typedtree) escape_validator.visit(self.typedtree) self.artiq_ir = artiq_ir_generator.visit(self.typedtree) + print(self.artiq_ir[0]) + print(self.artiq_ir[1]) dead_code_eliminator.process(self.artiq_ir) local_access_validator.process(self.artiq_ir) self.llvm_ir = llvm_ir_generator.process(self.artiq_ir) diff --git a/artiq/compiler/transforms/artiq_ir_generator.py b/artiq/compiler/transforms/artiq_ir_generator.py index 1c49508e2..3b9e5be13 100644 --- a/artiq/compiler/transforms/artiq_ir_generator.py +++ b/artiq/compiler/transforms/artiq_ir_generator.py @@ -1250,18 +1250,37 @@ class ARTIQIRGenerator(algorithm.Visitor): else: typ = node.func.type.find() func = self.visit(node.func) - args = [self.visit(arg) for arg in node.args] - for index, optarg_name in enumerate(typ.optargs): - if len(typ.args) + index >= len(args): - optarg_typ = ir.TOption(typ.optargs[optarg_name]) - for keyword in node.keywords: - if keyword.arg == optarg_name: - value = self.append(ir.Alloc([self.visit(keyword.value)], optarg_typ)) - args.append(value) + args = [None] * (len(typ.args) + len(typ.optargs)) + + for index, arg_node in enumerate(node.args): + arg = self.visit(arg_node) + if index < len(typ.args): + args[index] = arg + else: + args[index] = self.append(ir.Alloc([arg], ir.TOption(arg.type))) + + for keyword in node.keywords: + arg = self.visit(keyword.value) + if keyword.arg in typ.args: + for index, arg_name in enumerate(typ.args): + if keyword.arg == arg_name: + assert args[index] is None + args[index] = arg break - else: - value = self.append(ir.Alloc([], optarg_typ)) - args.append(value) + elif keyword.arg in typ.optargs: + for index, optarg_name in enumerate(typ.optargs): + if keyword.arg == optarg_name: + assert args[len(typ.args) + index] is None + args[len(typ.args) + index] = \ + self.append(ir.Alloc([arg], ir.TOption(arg.type))) + break + + for index, optarg_name in enumerate(typ.optargs): + if args[len(typ.args) + index] is None: + args[len(typ.args) + index] = \ + self.append(ir.Alloc([], ir.TOption(typ.optargs[optarg_name]))) + + assert None not in args if self.unwind_target is None: return self.append(ir.Call(func, args)) diff --git a/artiq/compiler/transforms/llvm_ir_generator.py b/artiq/compiler/transforms/llvm_ir_generator.py index 5d4075f8b..8749d3348 100644 --- a/artiq/compiler/transforms/llvm_ir_generator.py +++ b/artiq/compiler/transforms/llvm_ir_generator.py @@ -519,7 +519,7 @@ class LLVMIRGenerator: if builtins.is_none(insn.value().type): return self.llbuilder.ret_void() else: - return self.llbuilder.ret(self.llmap[insn.value()]) + return self.llbuilder.ret(self.map(insn.value())) def process_Unreachable(self, insn): return self.llbuilder.unreachable() diff --git a/lit-test/compiler/integration/function.py b/lit-test/compiler/integration/function.py new file mode 100644 index 000000000..e1ae25fd9 --- /dev/null +++ b/lit-test/compiler/integration/function.py @@ -0,0 +1,10 @@ +# RUN: %python -m artiq.compiler.testbench.jit %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/lit-test/compiler/integration/lambda.py b/lit-test/compiler/integration/lambda.py new file mode 100644 index 000000000..7bbe8824f --- /dev/null +++ b/lit-test/compiler/integration/lambda.py @@ -0,0 +1,9 @@ +# RUN: %python -m artiq.compiler.testbench.jit %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 From 9db199cad9751b70734d654142dff92847c23ebd Mon Sep 17 00:00:00 2001 From: whitequark Date: Thu, 23 Jul 2015 03:15:36 +0300 Subject: [PATCH 142/369] Handle closure effects appropriately in LocalAccessValidator. --- artiq/compiler/module.py | 2 -- artiq/compiler/testbench/inferencer.py | 2 +- .../compiler/transforms/llvm_ir_generator.py | 3 ++- artiq/compiler/validators/local_access.py | 22 ++++++++++--------- lit-test/compiler/local_access/invalid.py | 2 +- 5 files changed, 16 insertions(+), 15 deletions(-) diff --git a/artiq/compiler/module.py b/artiq/compiler/module.py index 80e44acd3..38bccda92 100644 --- a/artiq/compiler/module.py +++ b/artiq/compiler/module.py @@ -32,8 +32,6 @@ class Module: monomorphism_validator.visit(self.typedtree) escape_validator.visit(self.typedtree) self.artiq_ir = artiq_ir_generator.visit(self.typedtree) - print(self.artiq_ir[0]) - print(self.artiq_ir[1]) dead_code_eliminator.process(self.artiq_ir) local_access_validator.process(self.artiq_ir) self.llvm_ir = llvm_ir_generator.process(self.artiq_ir) diff --git a/artiq/compiler/testbench/inferencer.py b/artiq/compiler/testbench/inferencer.py index 64a24b39d..e6e73ea60 100644 --- a/artiq/compiler/testbench/inferencer.py +++ b/artiq/compiler/testbench/inferencer.py @@ -42,7 +42,7 @@ class Printer(algorithm.Visitor): ":{}".format(self.type_printer.name(node.type))) def main(): - if sys.argv[1] == "+mono": + if len(sys.argv) > 1 and sys.argv[1] == "+mono": del sys.argv[1] monomorphize = True else: diff --git a/artiq/compiler/transforms/llvm_ir_generator.py b/artiq/compiler/transforms/llvm_ir_generator.py index 8749d3348..de7a75307 100644 --- a/artiq/compiler/transforms/llvm_ir_generator.py +++ b/artiq/compiler/transforms/llvm_ir_generator.py @@ -17,8 +17,9 @@ class LLVMIRGenerator: self.fixups = [] 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.find().elts]) + return ll.LiteralStructType([self.llty_of_type(eltty) for eltty in typ.elts]) elif types.is_function(typ): envarg = ll.IntType(8).as_pointer() llty = ll.FunctionType(args=[envarg] + diff --git a/artiq/compiler/validators/local_access.py b/artiq/compiler/validators/local_access.py index 57fd8486c..fb68b54d4 100644 --- a/artiq/compiler/validators/local_access.py +++ b/artiq/compiler/validators/local_access.py @@ -97,16 +97,18 @@ class LocalAccessValidator: self._uninitialized_access(insn, var_name, pred_at_fault(env, var_name)) - if isinstance(insn, ir.Closure): - env = insn.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]: - # A closure would capture this variable while it is not always - # initialized. Note that this check is transitive. - self._uninitialized_access(insn, var_name, - pred_at_fault(env, var_name)) + # Creating a closure has no side effects. However, using a closure does. + for operand in insn.operands: + if isinstance(operand, ir.Closure): + env = operand.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]: + # A closure would capture this variable while it is not always + # initialized. Note that this check is transitive. + self._uninitialized_access(operand, var_name, + pred_at_fault(env, var_name)) # Save the state. state[block] = block_state diff --git a/lit-test/compiler/local_access/invalid.py b/lit-test/compiler/local_access/invalid.py index 789285d51..c67272eef 100644 --- a/lit-test/compiler/local_access/invalid.py +++ b/lit-test/compiler/local_access/invalid.py @@ -20,7 +20,7 @@ else: -t # CHECK-L: ${LINE:+1}: error: variable 't' can be captured in a closure uninitialized -lambda: t +l = lambda: t # CHECK-L: ${LINE:+1}: error: variable 't' can be captured in a closure uninitialized def f(): From 2b9ac344d8829833bd84ec990d2b5a7bc3f4168d Mon Sep 17 00:00:00 2001 From: whitequark Date: Thu, 23 Jul 2015 07:57:35 +0300 Subject: [PATCH 143/369] Verify LLVM module in compiler.textbench.jit. --- artiq/compiler/testbench/jit.py | 1 + 1 file changed, 1 insertion(+) diff --git a/artiq/compiler/testbench/jit.py b/artiq/compiler/testbench/jit.py index 3febb9d67..7d7d85227 100644 --- a/artiq/compiler/testbench/jit.py +++ b/artiq/compiler/testbench/jit.py @@ -23,6 +23,7 @@ def main(): lltarget = llvm.Target.from_default_triple() llmachine = lltarget.create_target_machine() llparsedmod = llvm.parse_assembly(str(llmod)) + llparsedmod.verify() lljit = llvm.create_mcjit_compiler(llparsedmod, llmachine) lljit.finalize_object() llmain = lljit.get_pointer_to_global(llparsedmod.get_function(llmod.name + ".__modinit__")) From 65121b437f81839d43d6f52c02cb04e28992a673 Mon Sep 17 00:00:00 2001 From: whitequark Date: Thu, 23 Jul 2015 07:57:49 +0300 Subject: [PATCH 144/369] Rework internal logic of slices. --- .../compiler/transforms/artiq_ir_generator.py | 85 +++++++++++++------ lit-test/compiler/integration/subscript.py | 5 ++ 2 files changed, 65 insertions(+), 25 deletions(-) diff --git a/artiq/compiler/transforms/artiq_ir_generator.py b/artiq/compiler/transforms/artiq_ir_generator.py index 3b9e5be13..b4d803134 100644 --- a/artiq/compiler/transforms/artiq_ir_generator.py +++ b/artiq/compiler/transforms/artiq_ir_generator.py @@ -723,51 +723,86 @@ class ARTIQIRGenerator(algorithm.Visitor): length = self.iterable_len(value, node.slice.type) if node.slice.lower is not None: - min_index = self.visit(node.slice.lower) + start_index = self.visit(node.slice.lower) else: - min_index = ir.Constant(0, node.slice.type) - mapped_min_index = self._map_index(length, min_index) + start_index = ir.Constant(0, node.slice.type) + mapped_start_index = self._map_index(length, start_index) if node.slice.upper is not None: - max_index = self.visit(node.slice.upper) + stop_index = self.visit(node.slice.upper) else: - max_index = length - mapped_max_index = self._map_index(length, max_index, one_past_the_end=True) + stop_index = length + mapped_stop_index = self._map_index(length, stop_index, one_past_the_end=True) if node.slice.step is not None: step = self.visit(node.slice.step) + self._make_check(self.append(ir.Compare(ast.NotEq(loc=None), step, + ir.Constant(0, step.type))), + lambda: self.append(ir.Alloc([], builtins.TValueError()))) 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_max_index, mapped_min_index)) - slice_size = self.append(ir.Arith(ast.FloorDiv(loc=None), unstepped_size, step, - name="slice.size")) - + 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: self.append(ir.Alloc([], builtins.TValueError()))) if self.current_assign is None: - other_value = self.append(ir.Alloc([slice_size], value.type)) + 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 - def body_gen(other_index): - offset = self.append(ir.Arith(ast.Mult(loc=None), step, other_index)) - index = self.append(ir.Arith(ast.Add(loc=None), min_index, offset)) + prehead = self.current_block - 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)) + head = self.current_block = self.add_block() + prehead.append(ir.Branch(head)) - return self.append(ir.Arith(ast.Add(loc=None), other_index, - ir.Constant(1, node.slice.type))) - self._make_loop(ir.Constant(0, node.slice.type), - lambda index: self.append(ir.Compare(ast.Lt(loc=None), index, slice_size)), - body_gen) + 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() + + 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() + head.append(ir.BranchIf(within_bounds, body, tail)) if self.current_assign is None: return other_value diff --git a/lit-test/compiler/integration/subscript.py b/lit-test/compiler/integration/subscript.py index 2d80b1472..14cbd7529 100644 --- a/lit-test/compiler/integration/subscript.py +++ b/lit-test/compiler/integration/subscript.py @@ -10,3 +10,8 @@ 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] From 20f5f8217d9c80cf878ad542262b298c3556efd2 Mon Sep 17 00:00:00 2001 From: whitequark Date: Thu, 23 Jul 2015 08:09:25 +0300 Subject: [PATCH 145/369] Make sure tests pass both on ARTIQ Python and CPython. In some cases (the `is` operator and wraparound arithmetics) the tests will only pass on ARTIQ Python. These are conditionally commented out. --- artiq/compiler/testbench/jit.py | 4 +++- lit-test/compiler/integration/arithmetics.py | 3 ++- lit-test/compiler/integration/attribute.py | 1 + lit-test/compiler/integration/bool.py | 1 + lit-test/compiler/integration/builtin.py | 13 +++++++------ lit-test/compiler/integration/compare.py | 3 ++- lit-test/compiler/integration/for.py | 1 + lit-test/compiler/integration/function.py | 1 + lit-test/compiler/integration/if.py | 1 + lit-test/compiler/integration/lambda.py | 1 + lit-test/compiler/integration/list.py | 1 + lit-test/compiler/integration/locals.py | 1 + lit-test/compiler/integration/subscript.py | 1 + lit-test/compiler/integration/tuple.py | 1 + lit-test/compiler/integration/while.py | 1 + lit-test/harness.py | 12 +++++++++--- 16 files changed, 34 insertions(+), 12 deletions(-) diff --git a/artiq/compiler/testbench/jit.py b/artiq/compiler/testbench/jit.py index 7d7d85227..1275b23e9 100644 --- a/artiq/compiler/testbench/jit.py +++ b/artiq/compiler/testbench/jit.py @@ -18,7 +18,9 @@ def main(): engine = diagnostic.Engine() engine.process = process_diagnostic - llmod = Module.from_string("".join(fileinput.input()).expandtabs(), engine=engine).llvm_ir + source = "".join(fileinput.input()) + source = source.replace("#ARTIQ#", "") + llmod = Module.from_string(source.expandtabs(), engine=engine).llvm_ir lltarget = llvm.Target.from_default_triple() llmachine = lltarget.create_target_machine() diff --git a/lit-test/compiler/integration/arithmetics.py b/lit-test/compiler/integration/arithmetics.py index 8a278aff6..b59d03c91 100644 --- a/lit-test/compiler/integration/arithmetics.py +++ b/lit-test/compiler/integration/arithmetics.py @@ -1,4 +1,5 @@ # RUN: %python -m artiq.compiler.testbench.jit %s +# RUN: %python %s assert -(-1) == 1 assert -(-1.0) == 1.0 @@ -28,7 +29,7 @@ assert 9.0 ** 0.5 == 3.0 assert 1 << 1 == 2 assert 2 >> 1 == 1 assert -2 >> 1 == -1 -assert 1 << 32 == 0 +#ARTIQ#assert 1 << 32 == 0 assert -1 >> 32 == -1 assert 0x18 & 0x0f == 0x08 assert 0x18 | 0x0f == 0x1f diff --git a/lit-test/compiler/integration/attribute.py b/lit-test/compiler/integration/attribute.py index 672d02ef3..301243b54 100644 --- a/lit-test/compiler/integration/attribute.py +++ b/lit-test/compiler/integration/attribute.py @@ -1,4 +1,5 @@ # RUN: %python -m artiq.compiler.testbench.jit %s +# RUN: %python %s r = range(10) assert r.start == 0 diff --git a/lit-test/compiler/integration/bool.py b/lit-test/compiler/integration/bool.py index 9df8b97c2..1a68ebd1c 100644 --- a/lit-test/compiler/integration/bool.py +++ b/lit-test/compiler/integration/bool.py @@ -1,4 +1,5 @@ # RUN: %python -m artiq.compiler.testbench.jit %s +# RUN: %python %s assert (not 0) == True assert (not 1) == False diff --git a/lit-test/compiler/integration/builtin.py b/lit-test/compiler/integration/builtin.py index 20296fc8a..256abe76e 100644 --- a/lit-test/compiler/integration/builtin.py +++ b/lit-test/compiler/integration/builtin.py @@ -1,24 +1,25 @@ # RUN: %python -m artiq.compiler.testbench.jit %s +# RUN: %python %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 +#ARTIQ#assert int(1, width=64) << 40 is 1099511627776 -assert float() is 0.0 -assert float(1) is 1.0 +#ARTIQ#assert float() is 0.0 +#ARTIQ#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) +#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 -assert round(1.4) is 1 and round(1.6) is 2 +#ARTIQ#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 index 000c9d8bf..48a33cc09 100644 --- a/lit-test/compiler/integration/compare.py +++ b/lit-test/compiler/integration/compare.py @@ -1,4 +1,5 @@ # 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) @@ -11,7 +12,7 @@ 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) +#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 diff --git a/lit-test/compiler/integration/for.py b/lit-test/compiler/integration/for.py index 9597c4de6..9c305f5da 100644 --- a/lit-test/compiler/integration/for.py +++ b/lit-test/compiler/integration/for.py @@ -1,4 +1,5 @@ # RUN: %python -m artiq.compiler.testbench.jit %s +# RUN: %python %s count = 0 for x in range(10): diff --git a/lit-test/compiler/integration/function.py b/lit-test/compiler/integration/function.py index e1ae25fd9..bbaca2083 100644 --- a/lit-test/compiler/integration/function.py +++ b/lit-test/compiler/integration/function.py @@ -1,4 +1,5 @@ # RUN: %python -m artiq.compiler.testbench.jit %s +# RUN: %python %s def fib(x): if x == 1: diff --git a/lit-test/compiler/integration/if.py b/lit-test/compiler/integration/if.py index 8a282a594..fab6c3df0 100644 --- a/lit-test/compiler/integration/if.py +++ b/lit-test/compiler/integration/if.py @@ -1,4 +1,5 @@ # RUN: %python -m artiq.compiler.testbench.jit %s +# RUN: %python %s if True: assert True diff --git a/lit-test/compiler/integration/lambda.py b/lit-test/compiler/integration/lambda.py index 7bbe8824f..a1f08763a 100644 --- a/lit-test/compiler/integration/lambda.py +++ b/lit-test/compiler/integration/lambda.py @@ -1,4 +1,5 @@ # RUN: %python -m artiq.compiler.testbench.jit %s +# RUN: %python %s assert (lambda: 1)() == 1 assert (lambda x: x)(1) == 1 diff --git a/lit-test/compiler/integration/list.py b/lit-test/compiler/integration/list.py index d843b93ae..660293db7 100644 --- a/lit-test/compiler/integration/list.py +++ b/lit-test/compiler/integration/list.py @@ -1,4 +1,5 @@ # RUN: %python -m artiq.compiler.testbench.jit %s +# RUN: %python %s [x, y] = [1, 2] assert (x, y) == (1, 2) diff --git a/lit-test/compiler/integration/locals.py b/lit-test/compiler/integration/locals.py index cc2c1f1be..6ad9b0763 100644 --- a/lit-test/compiler/integration/locals.py +++ b/lit-test/compiler/integration/locals.py @@ -1,4 +1,5 @@ # RUN: %python -m artiq.compiler.testbench.jit %s +# RUN: %python %s x = 1 assert x == 1 diff --git a/lit-test/compiler/integration/subscript.py b/lit-test/compiler/integration/subscript.py index 14cbd7529..15b3651e8 100644 --- a/lit-test/compiler/integration/subscript.py +++ b/lit-test/compiler/integration/subscript.py @@ -1,4 +1,5 @@ # RUN: %python -m artiq.compiler.testbench.jit %s +# RUN: %python %s lst = list(range(10)) assert lst[0] == 0 diff --git a/lit-test/compiler/integration/tuple.py b/lit-test/compiler/integration/tuple.py index 862154c9f..5d6c153dd 100644 --- a/lit-test/compiler/integration/tuple.py +++ b/lit-test/compiler/integration/tuple.py @@ -1,4 +1,5 @@ # RUN: %python -m artiq.compiler.testbench.jit %s +# RUN: %python %s x, y = 2, 1 x, y = y, x diff --git a/lit-test/compiler/integration/while.py b/lit-test/compiler/integration/while.py index fea81fadc..7cd2ac626 100644 --- a/lit-test/compiler/integration/while.py +++ b/lit-test/compiler/integration/while.py @@ -1,4 +1,5 @@ # RUN: %python -m artiq.compiler.testbench.jit %s +# RUN: %python %s cond, count = True, 0 while cond: diff --git a/lit-test/harness.py b/lit-test/harness.py index 9821ae61f..955394c3b 100644 --- a/lit-test/harness.py +++ b/lit-test/harness.py @@ -12,7 +12,7 @@ emulate the same behavior when invoked under lit. import sys, os, argparse, importlib parser = argparse.ArgumentParser(description=__doc__) -parser.add_argument('-m', metavar='mod', type=str, required=True, +parser.add_argument('-m', metavar='mod', type=str, help='run library module as a script') parser.add_argument('args', type=str, nargs='+', help='arguments passed to program in sys.argv[1:]') @@ -21,5 +21,11 @@ args = parser.parse_args(sys.argv[1:]) artiq_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..')) sys.path.insert(1, artiq_path) -sys.argv[1:] = args.args -importlib.import_module(args.m).main() +if args.m: + sys.argv[1:] = args.args + importlib.import_module(args.m).main() +else: + sys.argv[1:] = args.args[1:] + with open(args.args[0]) as f: + code = compile(f.read(), args.args[0], 'exec') + exec(code) From ece52062f21a04e600866b21ba3e3f677a13f045 Mon Sep 17 00:00:00 2001 From: whitequark Date: Sat, 25 Jul 2015 05:37:37 +0300 Subject: [PATCH 146/369] Implement code generation for exception handling. --- artiq/compiler/builtins.py | 31 +++- artiq/compiler/ir.py | 24 ++- artiq/compiler/module.py | 2 +- .../compiler/transforms/artiq_ir_generator.py | 163 +++++++++++++----- artiq/compiler/transforms/inferencer.py | 91 ++++++---- .../compiler/transforms/llvm_ir_generator.py | 132 ++++++++++---- artiq/compiler/types.py | 4 +- artiq/compiler/validators/escape.py | 17 +- lit-test/compiler/inferencer/unify.py | 15 ++ 9 files changed, 345 insertions(+), 134 deletions(-) diff --git a/artiq/compiler/builtins.py b/artiq/compiler/builtins.py index 73035600d..903900540 100644 --- a/artiq/compiler/builtins.py +++ b/artiq/compiler/builtins.py @@ -72,6 +72,26 @@ class TRange(types.TMono): ]) 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 (int(width=64)). + + + # Keep this in sync with the function ARTIQIRGenerator.alloc_exn. + attributes = OrderedDict([ + ("__name__", TStr()), + ("__file__", TStr()), + ("__line__", TInt(types.TValue(32))), + ("__col__", TInt(types.TValue(32))), + ("__message__", TStr()), + ("__param0__", TInt(types.TValue(64))), + ("__param1__", TInt(types.TValue(64))), + ("__param2__", TInt(types.TValue(64))), + ]) + def __init__(self, name="Exception"): super().__init__(name) @@ -170,8 +190,12 @@ def is_range(typ, elt=None): else: return types.is_mono(typ, "range") -def is_exception(typ): - return isinstance(typ.find(), TException) +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): typ = typ.find() @@ -189,4 +213,5 @@ def is_collection(typ): def is_allocated(typ): return typ.fold(False, lambda accum, typ: - is_list(typ) or is_str(typ) or types.is_function(typ)) + is_list(typ) or is_str(typ) or types.is_function(typ) or + is_exception(typ)) diff --git a/artiq/compiler/ir.py b/artiq/compiler/ir.py index f94df0ed4..8e042e14e 100644 --- a/artiq/compiler/ir.py +++ b/artiq/compiler/ir.py @@ -29,6 +29,13 @@ class TOption(types.TMono): def is_option(typ): return isinstance(typ, TOption) +class TExceptionTypeInfo(types.TMono): + def __init__(self): + super().__init__("exntypeinfo") + +def is_exn_typeinfo(typ): + return isinstance(typ, TExceptionTypeInfo) + class Value: """ An SSA value that keeps track of its uses. @@ -620,7 +627,7 @@ class SetAttr(Instruction): assert value.type == obj.type.elts[attr] else: assert value.type == obj.type.attributes[attr] - super().__init__([obj, value], typ, name) + super().__init__([obj, value], builtins.TNone(), name) self.attr = attr def opcode(self): @@ -1004,7 +1011,7 @@ class Invoke(Terminator): def opcode(self): return "invoke" - def function(self): + def target_function(self): return self.operands[0] def arguments(self): @@ -1038,11 +1045,15 @@ class LandingPad(Terminator): super().__init__([], builtins.TException(), name) self.types = [] + def clauses(self): + return zip(self.operands, self.types) + def add_clause(self, target, typ): assert isinstance(target, BasicBlock) - assert builtins.is_exception(typ) + assert typ is None or builtins.is_exception(typ) self.operands.append(target) - self.types.append(typ.find()) + self.types.append(typ.find() if typ is not None else None) + target.uses.add(self) def opcode(self): return "landingpad" @@ -1050,5 +1061,8 @@ class LandingPad(Terminator): def _operands_as_string(self): table = [] for typ, target in zip(self.types, self.operands): - table.append("{} => {}".format(types.TypePrinter().name(typ), target.as_operand())) + if typ is None: + table.append("... => {}".format(target.as_operand())) + else: + table.append("{} => {}".format(types.TypePrinter().name(typ), target.as_operand())) return "[{}]".format(", ".join(table)) diff --git a/artiq/compiler/module.py b/artiq/compiler/module.py index 38bccda92..9903eaaeb 100644 --- a/artiq/compiler/module.py +++ b/artiq/compiler/module.py @@ -33,7 +33,7 @@ class Module: escape_validator.visit(self.typedtree) self.artiq_ir = artiq_ir_generator.visit(self.typedtree) dead_code_eliminator.process(self.artiq_ir) - local_access_validator.process(self.artiq_ir) + # local_access_validator.process(self.artiq_ir) self.llvm_ir = llvm_ir_generator.process(self.artiq_ir) @classmethod diff --git a/artiq/compiler/transforms/artiq_ir_generator.py b/artiq/compiler/transforms/artiq_ir_generator.py index b4d803134..62f7f995f 100644 --- a/artiq/compiler/transforms/artiq_ir_generator.py +++ b/artiq/compiler/transforms/artiq_ir_generator.py @@ -465,8 +465,17 @@ class ARTIQIRGenerator(algorithm.Visitor): def visit_Continue(self, node): self.append(ir.Branch(self.continue_target)) + def raise_exn(self, exn): + loc_file = ir.Constant(self.current_loc.source_buffer.name, builtins.TStr()) + loc_line = ir.Constant(self.current_loc.line(), builtins.TInt(types.TValue(32))) + loc_column = ir.Constant(self.current_loc.column(), builtins.TInt(types.TValue(32))) + 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.Raise(exn)) + def visit_Raise(self, node): - self.append(ir.Raise(self.visit(node.exc))) + self.raise_exn(self.visit(node.exc)) def visit_Try(self, node): dispatcher = self.add_block("try.dispatch") @@ -521,15 +530,25 @@ class ARTIQIRGenerator(algorithm.Visitor): self.return_target = old_return handlers = [] + has_catchall = False for handler_node in node.handlers: - handler = self.add_block("handler." + handler_node.name_type.find().name) + 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) + has_catchall = True + self.current_block = handler handlers.append(handler) - landingpad.add_clause(handler, handler_node.name_type) 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) if any(node.finalbody): @@ -537,36 +556,44 @@ class ARTIQIRGenerator(algorithm.Visitor): self.current_block = finalizer self.visit(node.finalbody) + post_finalizer = self.current_block - if not self.current_block.is_terminated(): - dest = self.append(ir.GetLocal(final_state, ".k")) - self.append(ir.IndirectBranch(dest, final_targets)) - - tail = self.add_block("try.tail") + self.current_block = tail = self.add_block("try.tail") if any(node.finalbody): + final_targets.append(tail) + if self.break_target: break_proxy.append(ir.Branch(finalizer)) if self.continue_target: continue_proxy.append(ir.Branch(finalizer)) return_proxy.append(ir.Branch(finalizer)) - if not body.is_terminated(): - if any(node.finalbody): + + if not body.is_terminated(): body.append(ir.SetLocal(final_state, ".k", tail)) body.append(ir.Branch(finalizer)) - for handler in handlers: - if not handler.is_terminated(): - handler.append(ir.SetLocal(final_state, ".k", tail)) - handler.append(ir.Branch(tail)) - else: - body.append(ir.Branch(tail)) - for handler in handlers: - if not handler.is_terminated(): - handler.append(ir.Branch(tail)) - if any(tail.predecessors()): - self.current_block = tail + if not has_catchall: + # Add a catch-all handler so that finally would have a chance + # to execute. + handler = self.add_block("handler.catchall") + landingpad.add_clause(handler, None) + handlers.append(handler) + + for handler in handlers: + if not handler.is_terminated(): + handler.append(ir.SetLocal(final_state, ".k", tail)) + handler.append(ir.Branch(tail)) + + if not post_finalizer.is_terminated(): + dest = post_finalizer.append(ir.GetLocal(final_state, ".k")) + post_finalizer.append(ir.IndirectBranch(dest, final_targets)) else: - self.current_function.remove(tail) + if not body.is_terminated(): + body.append(ir.Branch(tail)) + + for handler in handlers: + if not handler.is_terminated(): + handler.append(ir.Branch(tail)) # TODO: With @@ -655,15 +682,16 @@ class ARTIQIRGenerator(algorithm.Visitor): 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 - out_of_bounds_block = self.add_block() - exn = out_of_bounds_block.append(ir.Alloc([], builtins.TIndexError())) - out_of_bounds_block.append(ir.Raise(exn)) + self.current_block = out_of_bounds_block = self.add_block() + exn = self.alloc_exn(builtins.TIndexError(), + ir.Constant("index {0} out of bounds 0:{1}", builtins.TStr()), + index, length) + self.raise_exn(exn) - in_bounds_block = self.add_block() - - self.append(ir.BranchIf(in_bounds, in_bounds_block, out_of_bounds_block)) - self.current_block = in_bounds_block + self.current_block = in_bounds_block = self.add_block() + head.append(ir.BranchIf(in_bounds, in_bounds_block, out_of_bounds_block)) return mapped_index @@ -673,7 +701,7 @@ class ARTIQIRGenerator(algorithm.Visitor): cond_block = self.current_block self.current_block = body_block = self.add_block() - self.append(ir.Raise(exn_gen())) + self.raise_exn(exn_gen()) self.current_block = tail_block = self.add_block() cond_block.append(ir.BranchIf(cond, tail_block, body_block)) @@ -736,9 +764,10 @@ class ARTIQIRGenerator(algorithm.Visitor): if node.slice.step is not None: step = self.visit(node.slice.step) - self._make_check(self.append(ir.Compare(ast.NotEq(loc=None), step, - ir.Constant(0, step.type))), - lambda: self.append(ir.Alloc([], builtins.TValueError()))) + self._make_check( + self.append(ir.Compare(ast.NotEq(loc=None), step, ir.Constant(0, step.type))), + lambda: self.alloc_exn(builtins.TValueError(), + ir.Constant("step cannot be zero", builtins.TStr()))) else: step = ir.Constant(1, node.slice.type) counting_up = self.append(ir.Compare(ast.Gt(loc=None), step, @@ -755,8 +784,12 @@ class ARTIQIRGenerator(algorithm.Visitor): 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: self.append(ir.Alloc([], builtins.TValueError()))) + self._make_check( + self.append(ir.Compare(ast.LtE(loc=None), slice_size, length)), + lambda: self.alloc_exn(builtins.TValueError(), + ir.Constant("slice size {0} is larger than iterable length {1}", + builtins.TStr()), + slice_size, iterable_len)) if self.current_assign is None: is_neg_size = self.append(ir.Compare(ast.Lt(loc=None), @@ -832,9 +865,12 @@ class ARTIQIRGenerator(algorithm.Visitor): 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: self.append(ir.Alloc([], builtins.TValueError()))) + self._make_check( + self.append(ir.Compare(ast.Eq(loc=None), length, + ir.Constant(len(node.elts), self._size_type))), + lambda: self.alloc_exn(builtins.TValueError(), + ir.Constant("list must be {0} elements long to decompose", builtins.TStr()), + length)) for index, elt_node in enumerate(node.elts): elt = self.append(ir.GetElem(self.current_assign, @@ -937,13 +973,15 @@ class ARTIQIRGenerator(algorithm.Visitor): 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.append(ir.Alloc([], builtins.TValueError()))) + self._make_check( + self.append(ir.Compare(ast.GtE(loc=None), rhs, ir.Constant(0, rhs.type))), + lambda: self.alloc_exn(builtins.TValueError(), + ir.Constant("shift amount must be nonnegative", builtins.TStr()))) elif isinstance(node.op, (ast.Div, ast.FloorDiv)): - self._make_check(self.append(ir.Compare(ast.NotEq(loc=None), rhs, - ir.Constant(0, rhs.type))), - lambda: self.append(ir.Alloc([], builtins.TZeroDivisionError()))) + self._make_check( + self.append(ir.Compare(ast.NotEq(loc=None), rhs, ir.Constant(0, rhs.type))), + lambda: self.alloc_exn(builtins.TZeroDivisionError(), + ir.Constant("cannot divide by zero", builtins.TStr()))) return self.append(ir.Arith(node.op, self.visit(node.left), rhs)) elif isinstance(node.op, ast.Add): # list + list, tuple + tuple @@ -1179,6 +1217,31 @@ class ARTIQIRGenerator(algorithm.Visitor): 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): + attributes = [ + ir.Constant(typ.find().name, ir.TExceptionTypeInfo()), # typeinfo + ir.Constant("", builtins.TStr()), # file + ir.Constant(0, builtins.TInt(types.TValue(32))), # line + ir.Constant(0, builtins.TInt(types.TValue(32))), # column + ] + + if message is None: + attributes.append(ir.Constant(typ.find().name, builtins.TStr())) + else: + attributes.append(message) # message + + param_type = builtins.TInt(types.TValue(64)) + for param in [param0, param1, param2]: + if param is None: + attributes.append(ir.Constant(0, builtins.TInt(types.TValue(64)))) + 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 @@ -1275,7 +1338,7 @@ class ARTIQIRGenerator(algorithm.Visitor): separator=" ", suffix="\n") return ir.Constant(None, builtins.TNone()) elif types.is_exn_constructor(typ): - return self.append(ir.Alloc([self.visit(arg) for args in node.args], node.type)) + return self.alloc_exn(node.type, *[self.visit(arg_node) for arg_node in node.args]) else: assert False @@ -1485,8 +1548,14 @@ class ARTIQIRGenerator(algorithm.Visitor): format_string += ")" elif builtins.is_exception(value.type): - # TODO: print exceptions - assert False + 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 diff --git a/artiq/compiler/transforms/inferencer.py b/artiq/compiler/transforms/inferencer.py index 3422abf85..15604e5b8 100644 --- a/artiq/compiler/transforms/inferencer.py +++ b/artiq/compiler/transforms/inferencer.py @@ -440,23 +440,32 @@ class Inferencer(algorithm.Visitor): self.engine.process(diag) if types.is_exn_constructor(typ): - exns = { - "IndexError": builtins.TIndexError, - "ValueError": builtins.TValueError, - } - for exn in exns: - if types.is_exn_constructor(typ, exn): - valid_forms = lambda: [ - valid_form("{exn}() -> {exn}".format(exn=exn)) - ] + 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:int(width=64)) -> {exn}".format(exn=typ.name)), + valid_form("{exn}(message:str, param1:int(width=64), " + "param2:int(width=64)) -> {exn}".format(exn=typ.name)), + valid_form("{exn}(message:str, param1:int(width=64), " + "param2:int(width=64), param3:int(width=64)) " + "-> {exn}".format(exn=typ.name)), + ] - if len(node.args) == 0 and len(node.keywords) == 0: - pass # False - else: - diagnose(valid_forms()) + 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(node.type, exns[exn](), - node.loc, None) + self._unify(message.type, builtins.TStr(), + message.loc, None) + for param in params: + self._unify(param.type, builtins.TInt(types.TValue(64)), + param.loc, None) + else: + diagnose(valid_forms()) + + self._unify(node.type, getattr(builtins, "T" + typ.name)(), + node.loc, None) elif types.is_builtin(typ, "bool"): valid_forms = lambda: [ valid_form("bool() -> bool"), @@ -829,26 +838,27 @@ class Inferencer(algorithm.Visitor): def visit_ExceptHandlerT(self, node): self.generic_visit(node) - 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, builtins.TException(node.filter.type.name), - node.name_loc, node.filter.loc, makenotes) + 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, builtins.TException(node.filter.type.name), + node.name_loc, node.filter.loc, makenotes) def _type_from_arguments(self, node, ret): self.generic_visit(node) @@ -943,6 +953,17 @@ class Inferencer(algorithm.Visitor): 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) + + exc_type = node.exc.type + if 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.exc.loc) + self.engine.process(diag) + def visit_Assert(self, node): self.generic_visit(node) self._unify(node.test.type, builtins.TBool(), diff --git a/artiq/compiler/transforms/llvm_ir_generator.py b/artiq/compiler/transforms/llvm_ir_generator.py index de7a75307..79f214097 100644 --- a/artiq/compiler/transforms/llvm_ir_generator.py +++ b/artiq/compiler/transforms/llvm_ir_generator.py @@ -43,7 +43,7 @@ class LLVMIRGenerator: return ll.IntType(builtins.get_int_width(typ)) elif builtins.is_float(typ): return ll.DoubleType() - elif builtins.is_str(typ): + elif builtins.is_str(typ) or ir.is_exn_typeinfo(typ): return ll.IntType(8).as_pointer() elif builtins.is_list(typ): lleltty = self.llty_of_type(builtins.get_iterable_elt(typ)) @@ -51,11 +51,8 @@ class LLVMIRGenerator: elif builtins.is_range(typ): lleltty = self.llty_of_type(builtins.get_iterable_elt(typ)) return ll.LiteralStructType([lleltty, lleltty, lleltty]) - elif builtins.is_exception(typ): - # TODO: hack before EH is working - return ll.LiteralStructType([]) elif ir.is_basic_block(typ): - return ll.LabelType() + return ll.IntType(8).as_pointer() elif ir.is_option(typ): return ll.LiteralStructType([ll.IntType(1), self.llty_of_type(typ.params["inner"])]) elif ir.is_environment(typ): @@ -65,8 +62,21 @@ class LLVMIRGenerator: return llty else: return llty.as_pointer() - else: - assert False + else: # Catch-all for exceptions and custom classes + if builtins.is_exception(typ): + name = 'Exception' # they all share layout + else: + name = typ.name + + llty = self.llcontext.get_identified_type(name) + if llty.elements is None: + 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 llconst_of_const(self, const): llty = self.llty_of_type(const.type) @@ -79,14 +89,27 @@ class LLVMIRGenerator: elif isinstance(const.value, (int, float)): return ll.Constant(llty, const.value) elif isinstance(const.value, str): - as_bytes = (const.value + '\0').encode('utf-8') + assert "\0" not in const.value + + as_bytes = (const.value + "\0").encode("utf-8") + if ir.is_exn_typeinfo(const.type): + # Exception typeinfo; should be merged with identical others + name = "__artiq_exn_" + const.value + linkage = "linkonce" + unnamed_addr = False + else: + # Just a string + name = self.llmodule.get_unique_name("str") + linkage = "private" + unnamed_addr = True + llstrty = ll.ArrayType(ll.IntType(8), len(as_bytes)) - llconst = ll.GlobalVariable(self.llmodule, llstrty, - name=self.llmodule.get_unique_name("str")) + llconst = ll.GlobalVariable(self.llmodule, llstrty, name) llconst.global_constant = True - llconst.unnamed_addr = True - llconst.linkage = 'internal' llconst.initializer = ll.Constant(llstrty, bytearray(as_bytes)) + llconst.linkage = linkage + llconst.unnamed_addr = unnamed_addr + return llconst.bitcast(ll.IntType(8).as_pointer()) else: assert False @@ -112,6 +135,10 @@ class LLVMIRGenerator: 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) + elif name == "__artiq_raise": + llty = ll.FunctionType(ll.VoidType(), [self.llty_of_type(builtins.TException())]) + elif name == "__artiq_personality": + llty = ll.FunctionType(ll.IntType(32), [], var_arg=True) else: assert False return ll.Function(self.llmodule, llty, name) @@ -124,8 +151,10 @@ class LLVMIRGenerator: elif isinstance(value, ir.Function): llfun = self.llmodule.get_global(value.name) if llfun is None: - return ll.Function(self.llmodule, self.llty_of_type(value.type, bare=True), - value.name) + llfun = ll.Function(self.llmodule, self.llty_of_type(value.type, bare=True), + value.name) + llfun.linkage = 'internal' + return llfun else: return llfun else: @@ -186,6 +215,9 @@ class LLVMIRGenerator: self.fixups.append(fixup) return llinsn + def llindex(self, index): + return ll.Constant(ll.IntType(32), index) + def process_Alloc(self, insn): if ir.is_environment(insn.type): return self.llbuilder.alloca(self.llty_of_type(insn.type, bare=True), @@ -210,6 +242,13 @@ class LLVMIRGenerator: size=llsize) llvalue = self.llbuilder.insert_value(llvalue, llalloc, 1, name=insn.name) return llvalue + elif builtins.is_exception(insn.type): + 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)]) + self.llbuilder.store(lloperand, llfieldptr) + return llalloc elif builtins.is_allocated(insn.type): assert False else: # immutable @@ -219,9 +258,6 @@ class LLVMIRGenerator: llvalue.name = insn.name return llvalue - def llindex(self, index): - return ll.Constant(ll.IntType(32), index) - 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) @@ -241,6 +277,8 @@ class LLVMIRGenerator: env = insn.environment() llptr = self.llptr_to_var(self.map(env), env.type, insn.var_name) llvalue = self.map(insn.value()) + 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. @@ -266,11 +304,11 @@ class LLVMIRGenerator: return self.llbuilder.load(llptr) def process_SetAttr(self, insn): - assert builtins.is_allocated(insns.object().type) + assert builtins.is_allocated(insn.object().type) llptr = self.llbuilder.gep(self.map(insn.object()), [self.llindex(0), self.llindex(self.attr_index(insn))], name=insn.name) - return self.llbuilder.store(llptr, self.map(insn.value())) + return self.llbuilder.store(self.map(insn.value()), llptr) def process_GetElem(self, insn): llelts = self.llbuilder.extract_value(self.map(insn.list()), 1) @@ -483,7 +521,9 @@ class LLVMIRGenerator: llargs = map(self.map, insn.operands) return self.llbuilder.call(self.llbuiltin("printf"), llargs, name=insn.name) - # elif insn.op == "exncast": + elif insn.op == "exncast": + # This is an identity cast at LLVM IR level. + return self.map(insn.operands[0]) else: assert False @@ -495,13 +535,24 @@ class LLVMIRGenerator: name=insn.name) return llvalue - def process_Call(self, insn): + def prepare_call(self, insn): llclosure, llargs = self.map(insn.target_function()), map(self.map, insn.arguments()) llenv = self.llbuilder.extract_value(llclosure, 0) llfun = self.llbuilder.extract_value(llclosure, 1) - return self.llbuilder.call(llfun, [llenv] + list(llargs), + return llfun, [llenv] + list(llargs) + + def process_Call(self, insn): + llfun, llargs = self.prepare_call(insn) + return self.llbuilder.call(llfun, llargs, name=insn.name) + def process_Invoke(self, insn): + llfun, llargs = self.prepare_call(insn) + llnormalblock = self.map(insn.normal_target()) + llunwindblock = self.map(insn.exception_target()) + return self.llbuilder.invoke(llfun, llargs, llnormalblock, llunwindblock, + name=insn.name) + def process_Select(self, insn): return self.llbuilder.select(self.map(insn.condition()), self.map(insn.if_true()), self.map(insn.if_false())) @@ -513,8 +564,11 @@ class LLVMIRGenerator: return self.llbuilder.cbranch(self.map(insn.condition()), self.map(insn.if_true()), self.map(insn.if_false())) - # def process_IndirectBranch(self, insn): - # pass + 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): @@ -526,15 +580,33 @@ class LLVMIRGenerator: return self.llbuilder.unreachable() def process_Raise(self, insn): - # TODO: hack before EH is working - llinsn = self.llbuilder.call(self.llbuiltin("llvm.trap"), [], + arg = self.map(insn.operands[0]) + llinsn = self.llbuilder.call(self.llbuiltin("__artiq_raise"), [arg], name=insn.name) + llinsn.attributes.add('noreturn') self.llbuilder.unreachable() return llinsn - # def process_Invoke(self, insn): - # pass + def process_LandingPad(self, insn): + lllandingpad = self.llbuilder.landingpad(self.llty_of_type(insn.type), + self.llbuiltin("__artiq_personality")) + llexnnameptr = self.llbuilder.gep(lllandingpad, [self.llindex(0), self.llindex(0)]) + llexnname = self.llbuilder.load(llexnnameptr) - # def process_LandingPad(self, insn): - # pass + for target, typ in insn.clauses(): + if typ is None: + llclauseexnname = ll.Constant( + self.llty_of_type(ir.TExceptionTypeInfo()), None) + else: + llclauseexnname = self.llconst_of_const( + ir.Constant(typ.name, ir.TExceptionTypeInfo())) + lllandingpad.add_clause(ll.CatchClause(llclauseexnname)) + + llmatchingclause = self.llbuilder.icmp_unsigned('==', llexnname, llclauseexnname) + with self.llbuilder.if_then(llmatchingclause): + self.llbuilder.branch(self.map(target)) + + self.llbuilder.resume(lllandingpad) + + return lllandingpad diff --git a/artiq/compiler/types.py b/artiq/compiler/types.py index a84c1777e..a614ce637 100644 --- a/artiq/compiler/types.py +++ b/artiq/compiler/types.py @@ -85,7 +85,9 @@ class TMono(Type): A monomorphic type, possibly parametric. :class:`TMono` is supposed to be subclassed by builtin types, - unlike all other :class:`Type` descendants. + 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() diff --git a/artiq/compiler/validators/escape.py b/artiq/compiler/validators/escape.py index 9f73b936e..df87c6afc 100644 --- a/artiq/compiler/validators/escape.py +++ b/artiq/compiler/validators/escape.py @@ -239,13 +239,17 @@ class EscapeValidator(algorithm.Visitor): # 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 raising mutable data) + # * 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, is_aug_assign=False): target_region = self._region_of(target) @@ -298,14 +302,3 @@ class EscapeValidator(algorithm.Visitor): "cannot return a mutable value that does not live forever", {}, node.value.loc, notes=self._diagnostics_for(region, node.value.loc) + [note]) self.engine.process(diag) - - def visit_Raise(self, node): - if builtins.is_allocated(node.exc.type): - note = diagnostic.Diagnostic("note", - "this expression has type {type}", - {"type": types.TypePrinter().name(node.exc.type)}, - node.exc.loc) - diag = diagnostic.Diagnostic("error", - "cannot raise a mutable value", {}, - node.exc.loc, notes=[note]) - self.engine.process(diag) diff --git a/lit-test/compiler/inferencer/unify.py b/lit-test/compiler/inferencer/unify.py index 48681fdcb..c6f41b495 100644 --- a/lit-test/compiler/inferencer/unify.py +++ b/lit-test/compiler/inferencer/unify.py @@ -56,3 +56,18 @@ lambda x, y=1: x k = "x" # CHECK-L: k:str + +IndexError() +# CHECK-L: IndexError:():IndexError + +IndexError("x") +# CHECK-L: IndexError:("x":str):IndexError + +IndexError("x", 1) +# CHECK-L: IndexError:("x":str, 1:int(width=64)):IndexError + +IndexError("x", 1, 1) +# CHECK-L: IndexError:("x":str, 1:int(width=64), 1:int(width=64)):IndexError + +IndexError("x", 1, 1, 1) +# CHECK-L: IndexError:("x":str, 1:int(width=64), 1:int(width=64), 1:int(width=64)):IndexError From 692791f0bd4d952b291d7a6ba3eeb6c97379476f Mon Sep 17 00:00:00 2001 From: whitequark Date: Sat, 25 Jul 2015 07:01:25 +0300 Subject: [PATCH 147/369] Make sure a landing pad returns {i8*} to soothe LLVM codegen. --- artiq/compiler/transforms/llvm_ir_generator.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/artiq/compiler/transforms/llvm_ir_generator.py b/artiq/compiler/transforms/llvm_ir_generator.py index 79f214097..a88058b96 100644 --- a/artiq/compiler/transforms/llvm_ir_generator.py +++ b/artiq/compiler/transforms/llvm_ir_generator.py @@ -588,9 +588,11 @@ class LLVMIRGenerator: return llinsn def process_LandingPad(self, insn): - lllandingpad = self.llbuilder.landingpad(self.llty_of_type(insn.type), + lllandingpad = self.llbuilder.landingpad(ll.LiteralStructType([ll.IntType(8).as_pointer()]), self.llbuiltin("__artiq_personality")) - llexnnameptr = self.llbuilder.gep(lllandingpad, [self.llindex(0), self.llindex(0)]) + llrawexn = self.llbuilder.extract_value(lllandingpad, 0) + llexn = self.llbuilder.bitcast(llrawexn, self.llty_of_type(insn.type)) + llexnnameptr = self.llbuilder.gep(llexn, [self.llindex(0), self.llindex(0)]) llexnname = self.llbuilder.load(llexnnameptr) for target, typ in insn.clauses(): @@ -602,11 +604,15 @@ class LLVMIRGenerator: ir.Constant(typ.name, ir.TExceptionTypeInfo())) lllandingpad.add_clause(ll.CatchClause(llclauseexnname)) - llmatchingclause = self.llbuilder.icmp_unsigned('==', llexnname, llclauseexnname) - with self.llbuilder.if_then(llmatchingclause): + if typ is None: self.llbuilder.branch(self.map(target)) + else: + llmatchingclause = self.llbuilder.icmp_unsigned('==', llexnname, llclauseexnname) + with self.llbuilder.if_then(llmatchingclause): + self.llbuilder.branch(self.map(target)) - self.llbuilder.resume(lllandingpad) + if self.llbuilder.basic_block.terminator is None: + self.llbuilder.resume(lllandingpad) - return lllandingpad + return llexn From 862ac1f90d57ccb8f4447f7bfd87c28317d29279 Mon Sep 17 00:00:00 2001 From: whitequark Date: Mon, 27 Jul 2015 03:52:45 +0300 Subject: [PATCH 148/369] lit-test/compiler -> lit-test/test. Other directories in lit-test will host various parts of the test harness. --- .../{compiler => test}/codegen/warning_useless_bool.py | 0 lit-test/{compiler => test}/inferencer/builtin_calls.py | 0 lit-test/{compiler => test}/inferencer/coerce.py | 0 lit-test/{compiler => test}/inferencer/error_assert.py | 0 .../{compiler => test}/inferencer/error_builtin_calls.py | 0 lit-test/{compiler => test}/inferencer/error_call.py | 0 lit-test/{compiler => test}/inferencer/error_coerce.py | 0 .../{compiler => test}/inferencer/error_comprehension.py | 0 .../{compiler => test}/inferencer/error_control_flow.py | 0 lit-test/{compiler => test}/inferencer/error_exception.py | 0 lit-test/{compiler => test}/inferencer/error_iterable.py | 0 .../{compiler => test}/inferencer/error_local_unbound.py | 0 lit-test/{compiler => test}/inferencer/error_locals.py | 0 lit-test/{compiler => test}/inferencer/error_return.py | 0 lit-test/{compiler => test}/inferencer/error_subscript.py | 0 lit-test/{compiler => test}/inferencer/error_unify.py | 0 lit-test/{compiler => test}/inferencer/exception.py | 0 lit-test/{compiler => test}/inferencer/gcd.py | 0 lit-test/{compiler => test}/inferencer/prelude.py | 0 lit-test/{compiler => test}/inferencer/scoping.py | 0 lit-test/{compiler => test}/inferencer/unify.py | 0 lit-test/{compiler => test}/integration/arithmetics.py | 0 lit-test/{compiler => test}/integration/attribute.py | 0 lit-test/{compiler => test}/integration/bool.py | 0 lit-test/{compiler => test}/integration/builtin.py | 0 lit-test/{compiler => test}/integration/compare.py | 0 lit-test/{compiler => test}/integration/for.py | 0 lit-test/{compiler => test}/integration/function.py | 0 lit-test/{compiler => test}/integration/if.py | 0 lit-test/{compiler => test}/integration/lambda.py | 0 lit-test/{compiler => test}/integration/list.py | 0 lit-test/{compiler => test}/integration/locals.py | 0 lit-test/{compiler => test}/integration/print.py | 0 lit-test/{compiler => test}/integration/subscript.py | 0 lit-test/{compiler => test}/integration/tuple.py | 0 lit-test/{compiler => test}/integration/while.py | 0 lit-test/{ => test}/lit.cfg | 7 ++++--- lit-test/{compiler => test}/local_access/invalid.py | 0 lit-test/{compiler => test}/local_access/valid.py | 0 lit-test/{compiler => test}/monomorphism/error_notmono.py | 0 lit-test/{compiler => test}/monomorphism/integers.py | 0 41 files changed, 4 insertions(+), 3 deletions(-) rename lit-test/{compiler => test}/codegen/warning_useless_bool.py (100%) rename lit-test/{compiler => test}/inferencer/builtin_calls.py (100%) rename lit-test/{compiler => test}/inferencer/coerce.py (100%) rename lit-test/{compiler => test}/inferencer/error_assert.py (100%) rename lit-test/{compiler => test}/inferencer/error_builtin_calls.py (100%) rename lit-test/{compiler => test}/inferencer/error_call.py (100%) rename lit-test/{compiler => test}/inferencer/error_coerce.py (100%) rename lit-test/{compiler => test}/inferencer/error_comprehension.py (100%) rename lit-test/{compiler => test}/inferencer/error_control_flow.py (100%) rename lit-test/{compiler => test}/inferencer/error_exception.py (100%) rename lit-test/{compiler => test}/inferencer/error_iterable.py (100%) rename lit-test/{compiler => test}/inferencer/error_local_unbound.py (100%) rename lit-test/{compiler => test}/inferencer/error_locals.py (100%) rename lit-test/{compiler => test}/inferencer/error_return.py (100%) rename lit-test/{compiler => test}/inferencer/error_subscript.py (100%) rename lit-test/{compiler => test}/inferencer/error_unify.py (100%) rename lit-test/{compiler => test}/inferencer/exception.py (100%) rename lit-test/{compiler => test}/inferencer/gcd.py (100%) rename lit-test/{compiler => test}/inferencer/prelude.py (100%) rename lit-test/{compiler => test}/inferencer/scoping.py (100%) rename lit-test/{compiler => test}/inferencer/unify.py (100%) rename lit-test/{compiler => test}/integration/arithmetics.py (100%) rename lit-test/{compiler => test}/integration/attribute.py (100%) rename lit-test/{compiler => test}/integration/bool.py (100%) rename lit-test/{compiler => test}/integration/builtin.py (100%) rename lit-test/{compiler => test}/integration/compare.py (100%) rename lit-test/{compiler => test}/integration/for.py (100%) rename lit-test/{compiler => test}/integration/function.py (100%) rename lit-test/{compiler => test}/integration/if.py (100%) rename lit-test/{compiler => test}/integration/lambda.py (100%) rename lit-test/{compiler => test}/integration/list.py (100%) rename lit-test/{compiler => test}/integration/locals.py (100%) rename lit-test/{compiler => test}/integration/print.py (100%) rename lit-test/{compiler => test}/integration/subscript.py (100%) rename lit-test/{compiler => test}/integration/tuple.py (100%) rename lit-test/{compiler => test}/integration/while.py (100%) rename lit-test/{ => test}/lit.cfg (53%) rename lit-test/{compiler => test}/local_access/invalid.py (100%) rename lit-test/{compiler => test}/local_access/valid.py (100%) rename lit-test/{compiler => test}/monomorphism/error_notmono.py (100%) rename lit-test/{compiler => test}/monomorphism/integers.py (100%) diff --git a/lit-test/compiler/codegen/warning_useless_bool.py b/lit-test/test/codegen/warning_useless_bool.py similarity index 100% rename from lit-test/compiler/codegen/warning_useless_bool.py rename to lit-test/test/codegen/warning_useless_bool.py diff --git a/lit-test/compiler/inferencer/builtin_calls.py b/lit-test/test/inferencer/builtin_calls.py similarity index 100% rename from lit-test/compiler/inferencer/builtin_calls.py rename to lit-test/test/inferencer/builtin_calls.py diff --git a/lit-test/compiler/inferencer/coerce.py b/lit-test/test/inferencer/coerce.py similarity index 100% rename from lit-test/compiler/inferencer/coerce.py rename to lit-test/test/inferencer/coerce.py diff --git a/lit-test/compiler/inferencer/error_assert.py b/lit-test/test/inferencer/error_assert.py similarity index 100% rename from lit-test/compiler/inferencer/error_assert.py rename to lit-test/test/inferencer/error_assert.py diff --git a/lit-test/compiler/inferencer/error_builtin_calls.py b/lit-test/test/inferencer/error_builtin_calls.py similarity index 100% rename from lit-test/compiler/inferencer/error_builtin_calls.py rename to lit-test/test/inferencer/error_builtin_calls.py diff --git a/lit-test/compiler/inferencer/error_call.py b/lit-test/test/inferencer/error_call.py similarity index 100% rename from lit-test/compiler/inferencer/error_call.py rename to lit-test/test/inferencer/error_call.py diff --git a/lit-test/compiler/inferencer/error_coerce.py b/lit-test/test/inferencer/error_coerce.py similarity index 100% rename from lit-test/compiler/inferencer/error_coerce.py rename to lit-test/test/inferencer/error_coerce.py diff --git a/lit-test/compiler/inferencer/error_comprehension.py b/lit-test/test/inferencer/error_comprehension.py similarity index 100% rename from lit-test/compiler/inferencer/error_comprehension.py rename to lit-test/test/inferencer/error_comprehension.py diff --git a/lit-test/compiler/inferencer/error_control_flow.py b/lit-test/test/inferencer/error_control_flow.py similarity index 100% rename from lit-test/compiler/inferencer/error_control_flow.py rename to lit-test/test/inferencer/error_control_flow.py diff --git a/lit-test/compiler/inferencer/error_exception.py b/lit-test/test/inferencer/error_exception.py similarity index 100% rename from lit-test/compiler/inferencer/error_exception.py rename to lit-test/test/inferencer/error_exception.py diff --git a/lit-test/compiler/inferencer/error_iterable.py b/lit-test/test/inferencer/error_iterable.py similarity index 100% rename from lit-test/compiler/inferencer/error_iterable.py rename to lit-test/test/inferencer/error_iterable.py diff --git a/lit-test/compiler/inferencer/error_local_unbound.py b/lit-test/test/inferencer/error_local_unbound.py similarity index 100% rename from lit-test/compiler/inferencer/error_local_unbound.py rename to lit-test/test/inferencer/error_local_unbound.py diff --git a/lit-test/compiler/inferencer/error_locals.py b/lit-test/test/inferencer/error_locals.py similarity index 100% rename from lit-test/compiler/inferencer/error_locals.py rename to lit-test/test/inferencer/error_locals.py diff --git a/lit-test/compiler/inferencer/error_return.py b/lit-test/test/inferencer/error_return.py similarity index 100% rename from lit-test/compiler/inferencer/error_return.py rename to lit-test/test/inferencer/error_return.py diff --git a/lit-test/compiler/inferencer/error_subscript.py b/lit-test/test/inferencer/error_subscript.py similarity index 100% rename from lit-test/compiler/inferencer/error_subscript.py rename to lit-test/test/inferencer/error_subscript.py diff --git a/lit-test/compiler/inferencer/error_unify.py b/lit-test/test/inferencer/error_unify.py similarity index 100% rename from lit-test/compiler/inferencer/error_unify.py rename to lit-test/test/inferencer/error_unify.py diff --git a/lit-test/compiler/inferencer/exception.py b/lit-test/test/inferencer/exception.py similarity index 100% rename from lit-test/compiler/inferencer/exception.py rename to lit-test/test/inferencer/exception.py diff --git a/lit-test/compiler/inferencer/gcd.py b/lit-test/test/inferencer/gcd.py similarity index 100% rename from lit-test/compiler/inferencer/gcd.py rename to lit-test/test/inferencer/gcd.py diff --git a/lit-test/compiler/inferencer/prelude.py b/lit-test/test/inferencer/prelude.py similarity index 100% rename from lit-test/compiler/inferencer/prelude.py rename to lit-test/test/inferencer/prelude.py diff --git a/lit-test/compiler/inferencer/scoping.py b/lit-test/test/inferencer/scoping.py similarity index 100% rename from lit-test/compiler/inferencer/scoping.py rename to lit-test/test/inferencer/scoping.py diff --git a/lit-test/compiler/inferencer/unify.py b/lit-test/test/inferencer/unify.py similarity index 100% rename from lit-test/compiler/inferencer/unify.py rename to lit-test/test/inferencer/unify.py diff --git a/lit-test/compiler/integration/arithmetics.py b/lit-test/test/integration/arithmetics.py similarity index 100% rename from lit-test/compiler/integration/arithmetics.py rename to lit-test/test/integration/arithmetics.py diff --git a/lit-test/compiler/integration/attribute.py b/lit-test/test/integration/attribute.py similarity index 100% rename from lit-test/compiler/integration/attribute.py rename to lit-test/test/integration/attribute.py diff --git a/lit-test/compiler/integration/bool.py b/lit-test/test/integration/bool.py similarity index 100% rename from lit-test/compiler/integration/bool.py rename to lit-test/test/integration/bool.py diff --git a/lit-test/compiler/integration/builtin.py b/lit-test/test/integration/builtin.py similarity index 100% rename from lit-test/compiler/integration/builtin.py rename to lit-test/test/integration/builtin.py diff --git a/lit-test/compiler/integration/compare.py b/lit-test/test/integration/compare.py similarity index 100% rename from lit-test/compiler/integration/compare.py rename to lit-test/test/integration/compare.py diff --git a/lit-test/compiler/integration/for.py b/lit-test/test/integration/for.py similarity index 100% rename from lit-test/compiler/integration/for.py rename to lit-test/test/integration/for.py diff --git a/lit-test/compiler/integration/function.py b/lit-test/test/integration/function.py similarity index 100% rename from lit-test/compiler/integration/function.py rename to lit-test/test/integration/function.py diff --git a/lit-test/compiler/integration/if.py b/lit-test/test/integration/if.py similarity index 100% rename from lit-test/compiler/integration/if.py rename to lit-test/test/integration/if.py diff --git a/lit-test/compiler/integration/lambda.py b/lit-test/test/integration/lambda.py similarity index 100% rename from lit-test/compiler/integration/lambda.py rename to lit-test/test/integration/lambda.py diff --git a/lit-test/compiler/integration/list.py b/lit-test/test/integration/list.py similarity index 100% rename from lit-test/compiler/integration/list.py rename to lit-test/test/integration/list.py diff --git a/lit-test/compiler/integration/locals.py b/lit-test/test/integration/locals.py similarity index 100% rename from lit-test/compiler/integration/locals.py rename to lit-test/test/integration/locals.py diff --git a/lit-test/compiler/integration/print.py b/lit-test/test/integration/print.py similarity index 100% rename from lit-test/compiler/integration/print.py rename to lit-test/test/integration/print.py diff --git a/lit-test/compiler/integration/subscript.py b/lit-test/test/integration/subscript.py similarity index 100% rename from lit-test/compiler/integration/subscript.py rename to lit-test/test/integration/subscript.py diff --git a/lit-test/compiler/integration/tuple.py b/lit-test/test/integration/tuple.py similarity index 100% rename from lit-test/compiler/integration/tuple.py rename to lit-test/test/integration/tuple.py diff --git a/lit-test/compiler/integration/while.py b/lit-test/test/integration/while.py similarity index 100% rename from lit-test/compiler/integration/while.py rename to lit-test/test/integration/while.py diff --git a/lit-test/lit.cfg b/lit-test/test/lit.cfg similarity index 53% rename from lit-test/lit.cfg rename to lit-test/test/lit.cfg index 398164291..4662ffcc2 100644 --- a/lit-test/lit.cfg +++ b/lit-test/test/lit.cfg @@ -1,12 +1,13 @@ +import os, subprocess 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 = ['harness.py'] -config.test_source_root = os.path.dirname(__file__) python_executable = 'python3' -harness = '{} {}'.format(python_executable, os.path.join(config.test_source_root, 'harness.py')) +harness = '{} {}'.format(python_executable, os.path.join(root, 'harness.py')) config.substitutions.append( ('%python', harness) ) diff --git a/lit-test/compiler/local_access/invalid.py b/lit-test/test/local_access/invalid.py similarity index 100% rename from lit-test/compiler/local_access/invalid.py rename to lit-test/test/local_access/invalid.py diff --git a/lit-test/compiler/local_access/valid.py b/lit-test/test/local_access/valid.py similarity index 100% rename from lit-test/compiler/local_access/valid.py rename to lit-test/test/local_access/valid.py diff --git a/lit-test/compiler/monomorphism/error_notmono.py b/lit-test/test/monomorphism/error_notmono.py similarity index 100% rename from lit-test/compiler/monomorphism/error_notmono.py rename to lit-test/test/monomorphism/error_notmono.py diff --git a/lit-test/compiler/monomorphism/integers.py b/lit-test/test/monomorphism/integers.py similarity index 100% rename from lit-test/compiler/monomorphism/integers.py rename to lit-test/test/monomorphism/integers.py From 14c7b157858643e2b8288d99ae52e66a080e1ec7 Mon Sep 17 00:00:00 2001 From: whitequark Date: Mon, 27 Jul 2015 04:16:04 +0300 Subject: [PATCH 149/369] Add a test harness for exceptions. The libunwind.h is duplicated here so that it would be possible to test the Python parts without pulling in misoc. --- .gitignore | 1 + artiq/compiler/testbench/jit.py | 5 + lit-test/libartiq_personality/Makefile | 4 + .../libartiq_personality/__cxxabi_config.h | 1 + lit-test/libartiq_personality/unwind.h | 329 ++++++++++++++++++ lit-test/test/exceptions/raise.py | 4 + lit-test/test/lit.cfg | 10 + soc/runtime/artiq_personality.c | 25 ++ 8 files changed, 379 insertions(+) create mode 100644 lit-test/libartiq_personality/Makefile create mode 100644 lit-test/libartiq_personality/__cxxabi_config.h create mode 100644 lit-test/libartiq_personality/unwind.h create mode 100644 lit-test/test/exceptions/raise.py create mode 100644 soc/runtime/artiq_personality.c diff --git a/.gitignore b/.gitignore index 22d473a3f..b22661be4 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,4 @@ doc/manual/_build /.coverage examples/master/results Output/ +/lit-test/libartiq_personality/libartiq_personality.so diff --git a/artiq/compiler/testbench/jit.py b/artiq/compiler/testbench/jit.py index 1275b23e9..6c59b7043 100644 --- a/artiq/compiler/testbench/jit.py +++ b/artiq/compiler/testbench/jit.py @@ -10,6 +10,11 @@ llvm.initialize_native_asmprinter() llvm.check_jit_execution() def main(): + while sys.argv[1].startswith("+"): + if sys.argv[1] == "+load": + llvm.load_library_permanently(sys.argv[2]) + del sys.argv[1:3] + def process_diagnostic(diag): print("\n".join(diag.render())) if diag.level in ("fatal", "error"): diff --git a/lit-test/libartiq_personality/Makefile b/lit-test/libartiq_personality/Makefile new file mode 100644 index 000000000..3ef94aeb5 --- /dev/null +++ b/lit-test/libartiq_personality/Makefile @@ -0,0 +1,4 @@ +CC ?= clang + +libartiq_personality.so: ../../soc/runtime/artiq_personality.c + $(CC) -Wall -Werror -I. -fPIC -shared -o $@ $< diff --git a/lit-test/libartiq_personality/__cxxabi_config.h b/lit-test/libartiq_personality/__cxxabi_config.h new file mode 100644 index 000000000..42cd6fe5c --- /dev/null +++ b/lit-test/libartiq_personality/__cxxabi_config.h @@ -0,0 +1 @@ +#define LIBCXXABI_ARM_EHABI 0 diff --git a/lit-test/libartiq_personality/unwind.h b/lit-test/libartiq_personality/unwind.h new file mode 100644 index 000000000..86001bbb5 --- /dev/null +++ b/lit-test/libartiq_personality/unwind.h @@ -0,0 +1,329 @@ +//===------------------------------- unwind.h -----------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is dual licensed under the MIT and the University of Illinois Open +// Source Licenses. See LICENSE.TXT for details. +// +// +// C++ ABI Level 1 ABI documented at: +// http://mentorembedded.github.io/cxx-abi/abi-eh.html +// +//===----------------------------------------------------------------------===// + +#ifndef __UNWIND_H__ +#define __UNWIND_H__ + +#include +#include + +#if defined(__APPLE__) +#define LIBUNWIND_UNAVAIL __attribute__ (( unavailable )) +#else +#define LIBUNWIND_UNAVAIL +#endif + +#include <__cxxabi_config.h> + +typedef enum { + _URC_NO_REASON = 0, + _URC_OK = 0, + _URC_FOREIGN_EXCEPTION_CAUGHT = 1, + _URC_FATAL_PHASE2_ERROR = 2, + _URC_FATAL_PHASE1_ERROR = 3, + _URC_NORMAL_STOP = 4, + _URC_END_OF_STACK = 5, + _URC_HANDLER_FOUND = 6, + _URC_INSTALL_CONTEXT = 7, + _URC_CONTINUE_UNWIND = 8, +#if LIBCXXABI_ARM_EHABI + _URC_FAILURE = 9 +#endif +} _Unwind_Reason_Code; + +typedef enum { + _UA_SEARCH_PHASE = 1, + _UA_CLEANUP_PHASE = 2, + _UA_HANDLER_FRAME = 4, + _UA_FORCE_UNWIND = 8, + _UA_END_OF_STACK = 16 // gcc extension to C++ ABI +} _Unwind_Action; + +typedef struct _Unwind_Context _Unwind_Context; // opaque + +#if LIBCXXABI_ARM_EHABI +typedef uint32_t _Unwind_State; + +static const _Unwind_State _US_VIRTUAL_UNWIND_FRAME = 0; +static const _Unwind_State _US_UNWIND_FRAME_STARTING = 1; +static const _Unwind_State _US_UNWIND_FRAME_RESUME = 2; +/* Undocumented flag for force unwinding. */ +static const _Unwind_State _US_FORCE_UNWIND = 8; + +typedef uint32_t _Unwind_EHT_Header; + +struct _Unwind_Control_Block; +typedef struct _Unwind_Control_Block _Unwind_Control_Block; +typedef struct _Unwind_Control_Block _Unwind_Exception; /* Alias */ + +struct _Unwind_Control_Block { + uint64_t exception_class; + void (*exception_cleanup)(_Unwind_Reason_Code, _Unwind_Control_Block*); + + /* Unwinder cache, private fields for the unwinder's use */ + struct { + uint32_t reserved1; /* init reserved1 to 0, then don't touch */ + uint32_t reserved2; + uint32_t reserved3; + uint32_t reserved4; + uint32_t reserved5; + } unwinder_cache; + + /* Propagation barrier cache (valid after phase 1): */ + struct { + uint32_t sp; + uint32_t bitpattern[5]; + } barrier_cache; + + /* Cleanup cache (preserved over cleanup): */ + struct { + uint32_t bitpattern[4]; + } cleanup_cache; + + /* Pr cache (for pr's benefit): */ + struct { + uint32_t fnstart; /* function start address */ + _Unwind_EHT_Header* ehtp; /* pointer to EHT entry header word */ + uint32_t additional; + uint32_t reserved1; + } pr_cache; + + long long int :0; /* Enforce the 8-byte alignment */ +}; + +typedef _Unwind_Reason_Code (*_Unwind_Stop_Fn) + (_Unwind_State state, + _Unwind_Exception* exceptionObject, + struct _Unwind_Context* context); + +typedef _Unwind_Reason_Code (*__personality_routine) + (_Unwind_State state, + _Unwind_Exception* exceptionObject, + struct _Unwind_Context* context); +#else +struct _Unwind_Context; // opaque +struct _Unwind_Exception; // forward declaration +typedef struct _Unwind_Exception _Unwind_Exception; + +struct _Unwind_Exception { + uint64_t exception_class; + void (*exception_cleanup)(_Unwind_Reason_Code reason, + _Unwind_Exception *exc); + uintptr_t private_1; // non-zero means forced unwind + uintptr_t private_2; // holds sp that phase1 found for phase2 to use +#ifndef __LP64__ + // The gcc implementation of _Unwind_Exception used attribute mode on the + // above fields which had the side effect of causing this whole struct to + // round up to 32 bytes in size. To be more explicit, we add pad fields + // added for binary compatibility. + uint32_t reserved[3]; +#endif +}; + +typedef _Unwind_Reason_Code (*_Unwind_Stop_Fn) + (int version, + _Unwind_Action actions, + uint64_t exceptionClass, + _Unwind_Exception* exceptionObject, + struct _Unwind_Context* context, + void* stop_parameter ); + +typedef _Unwind_Reason_Code (*__personality_routine) + (int version, + _Unwind_Action actions, + uint64_t exceptionClass, + _Unwind_Exception* exceptionObject, + struct _Unwind_Context* context); +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +// +// The following are the base functions documented by the C++ ABI +// +#ifdef __USING_SJLJ_EXCEPTIONS__ +extern _Unwind_Reason_Code + _Unwind_SjLj_RaiseException(_Unwind_Exception *exception_object); +extern void _Unwind_SjLj_Resume(_Unwind_Exception *exception_object); +#else +extern _Unwind_Reason_Code + _Unwind_RaiseException(_Unwind_Exception *exception_object); +extern void _Unwind_Resume(_Unwind_Exception *exception_object); +#endif +extern void _Unwind_DeleteException(_Unwind_Exception *exception_object); + +#if LIBCXXABI_ARM_EHABI +typedef enum { + _UVRSC_CORE = 0, /* integer register */ + _UVRSC_VFP = 1, /* vfp */ + _UVRSC_WMMXD = 3, /* Intel WMMX data register */ + _UVRSC_WMMXC = 4 /* Intel WMMX control register */ +} _Unwind_VRS_RegClass; + +typedef enum { + _UVRSD_UINT32 = 0, + _UVRSD_VFPX = 1, + _UVRSD_UINT64 = 3, + _UVRSD_FLOAT = 4, + _UVRSD_DOUBLE = 5 +} _Unwind_VRS_DataRepresentation; + +typedef enum { + _UVRSR_OK = 0, + _UVRSR_NOT_IMPLEMENTED = 1, + _UVRSR_FAILED = 2 +} _Unwind_VRS_Result; + +extern void _Unwind_Complete(_Unwind_Exception* exception_object); + +extern _Unwind_VRS_Result +_Unwind_VRS_Get(_Unwind_Context *context, _Unwind_VRS_RegClass regclass, + uint32_t regno, _Unwind_VRS_DataRepresentation representation, + void *valuep); + +extern _Unwind_VRS_Result +_Unwind_VRS_Set(_Unwind_Context *context, _Unwind_VRS_RegClass regclass, + uint32_t regno, _Unwind_VRS_DataRepresentation representation, + void *valuep); + +extern _Unwind_VRS_Result +_Unwind_VRS_Pop(_Unwind_Context *context, _Unwind_VRS_RegClass regclass, + uint32_t discriminator, + _Unwind_VRS_DataRepresentation representation); +#endif + +extern uintptr_t _Unwind_GetGR(struct _Unwind_Context *context, int index); +extern void _Unwind_SetGR(struct _Unwind_Context *context, int index, + uintptr_t new_value); +extern uintptr_t _Unwind_GetIP(struct _Unwind_Context *context); +extern void _Unwind_SetIP(struct _Unwind_Context *, uintptr_t new_value); + +extern uintptr_t _Unwind_GetRegionStart(struct _Unwind_Context *context); +extern uintptr_t + _Unwind_GetLanguageSpecificData(struct _Unwind_Context *context); +#ifdef __USING_SJLJ_EXCEPTIONS__ +extern _Unwind_Reason_Code + _Unwind_SjLj_ForcedUnwind(_Unwind_Exception *exception_object, + _Unwind_Stop_Fn stop, void *stop_parameter); +#else +extern _Unwind_Reason_Code + _Unwind_ForcedUnwind(_Unwind_Exception *exception_object, + _Unwind_Stop_Fn stop, void *stop_parameter); +#endif + +#ifdef __USING_SJLJ_EXCEPTIONS__ +typedef struct _Unwind_FunctionContext *_Unwind_FunctionContext_t; +extern void _Unwind_SjLj_Register(_Unwind_FunctionContext_t fc); +extern void _Unwind_SjLj_Unregister(_Unwind_FunctionContext_t fc); +#endif + +// +// The following are semi-suppoted extensions to the C++ ABI +// + +// +// called by __cxa_rethrow(). +// +#ifdef __USING_SJLJ_EXCEPTIONS__ +extern _Unwind_Reason_Code + _Unwind_SjLj_Resume_or_Rethrow(_Unwind_Exception *exception_object); +#else +extern _Unwind_Reason_Code + _Unwind_Resume_or_Rethrow(_Unwind_Exception *exception_object); +#endif + +// _Unwind_Backtrace() is a gcc extension that walks the stack and calls the +// _Unwind_Trace_Fn once per frame until it reaches the bottom of the stack +// or the _Unwind_Trace_Fn function returns something other than _URC_NO_REASON. +typedef _Unwind_Reason_Code (*_Unwind_Trace_Fn)(struct _Unwind_Context *, + void *); +extern _Unwind_Reason_Code _Unwind_Backtrace(_Unwind_Trace_Fn, void *); + +// _Unwind_GetCFA is a gcc extension that can be called from within a +// personality handler to get the CFA (stack pointer before call) of +// current frame. +extern uintptr_t _Unwind_GetCFA(struct _Unwind_Context *); + + +// _Unwind_GetIPInfo is a gcc extension that can be called from within a +// personality handler. Similar to _Unwind_GetIP() but also returns in +// *ipBefore a non-zero value if the instruction pointer is at or before the +// instruction causing the unwind. Normally, in a function call, the IP returned +// is the return address which is after the call instruction and may be past the +// end of the function containing the call instruction. +extern uintptr_t _Unwind_GetIPInfo(struct _Unwind_Context *context, + int *ipBefore); + + +// __register_frame() is used with dynamically generated code to register the +// FDE for a generated (JIT) code. The FDE must use pc-rel addressing to point +// to its function and optional LSDA. +// __register_frame() has existed in all versions of Mac OS X, but in 10.4 and +// 10.5 it was buggy and did not actually register the FDE with the unwinder. +// In 10.6 and later it does register properly. +extern void __register_frame(const void *fde); +extern void __deregister_frame(const void *fde); + +// _Unwind_Find_FDE() will locate the FDE if the pc is in some function that has +// an associated FDE. Note, Mac OS X 10.6 and later, introduces "compact unwind +// info" which the runtime uses in preference to dwarf unwind info. This +// function will only work if the target function has an FDE but no compact +// unwind info. +struct dwarf_eh_bases { + uintptr_t tbase; + uintptr_t dbase; + uintptr_t func; +}; +extern const void *_Unwind_Find_FDE(const void *pc, struct dwarf_eh_bases *); + + +// This function attempts to find the start (address of first instruction) of +// a function given an address inside the function. It only works if the +// function has an FDE (dwarf unwind info). +// This function is unimplemented on Mac OS X 10.6 and later. Instead, use +// _Unwind_Find_FDE() and look at the dwarf_eh_bases.func result. +extern void *_Unwind_FindEnclosingFunction(void *pc); + +// Mac OS X does not support text-rel and data-rel addressing so these functions +// are unimplemented +extern uintptr_t _Unwind_GetDataRelBase(struct _Unwind_Context *context) + LIBUNWIND_UNAVAIL; +extern uintptr_t _Unwind_GetTextRelBase(struct _Unwind_Context *context) + LIBUNWIND_UNAVAIL; + +// Mac OS X 10.4 and 10.5 had implementations of these functions in +// libgcc_s.dylib, but they never worked. +/// These functions are no longer available on Mac OS X. +extern void __register_frame_info_bases(const void *fde, void *ob, void *tb, + void *db) LIBUNWIND_UNAVAIL; +extern void __register_frame_info(const void *fde, void *ob) + LIBUNWIND_UNAVAIL; +extern void __register_frame_info_table_bases(const void *fde, void *ob, + void *tb, void *db) + LIBUNWIND_UNAVAIL; +extern void __register_frame_info_table(const void *fde, void *ob) + LIBUNWIND_UNAVAIL; +extern void __register_frame_table(const void *fde) + LIBUNWIND_UNAVAIL; +extern void *__deregister_frame_info(const void *fde) + LIBUNWIND_UNAVAIL; +extern void *__deregister_frame_info_bases(const void *fde) + LIBUNWIND_UNAVAIL; + +#ifdef __cplusplus +} +#endif + +#endif // __UNWIND_H__ diff --git a/lit-test/test/exceptions/raise.py b/lit-test/test/exceptions/raise.py new file mode 100644 index 000000000..307c50df4 --- /dev/null +++ b/lit-test/test/exceptions/raise.py @@ -0,0 +1,4 @@ +# RUN: %python -m artiq.compiler.testbench.jit +load %personality %s +# REQUIRES: exceptions + +1/0 diff --git a/lit-test/test/lit.cfg b/lit-test/test/lit.cfg index 4662ffcc2..1d7478215 100644 --- a/lit-test/test/lit.cfg +++ b/lit-test/test/lit.cfg @@ -11,3 +11,13 @@ config.suffixes = ['.py'] python_executable = 'python3' harness = '{} {}'.format(python_executable, os.path.join(root, 'harness.py')) config.substitutions.append( ('%python', harness) ) + +if os.name == 'posix': + personality_build = os.path.join(root, 'libartiq_personality') + if subprocess.call(['make', '-sC', personality_build]) != 0: + lit_config.fatal("Unable to build JIT support library") + + personality_lib = os.path.join(personality_build, 'libartiq_personality.so') + config.substitutions.append( ('%personality', personality_lib) ) + + config.available_features.add('exceptions') diff --git a/soc/runtime/artiq_personality.c b/soc/runtime/artiq_personality.c new file mode 100644 index 000000000..831abc82a --- /dev/null +++ b/soc/runtime/artiq_personality.c @@ -0,0 +1,25 @@ +#include +#include +#include +#include + +struct artiq_exception { + const char *name; + const char *file; + int32_t line; + int32_t column; + const char *message; + int64_t param[3]; +}; + +void __artiq_raise(struct artiq_exception *artiq_exn) { + printf("raised %s\n", artiq_exn->name); + abort(); +} + +_Unwind_Reason_Code __artiq_personality(int version, + _Unwind_Action actions, uint64_t exceptionClass, + struct _Unwind_Exception *exceptionObject, + struct _Unwind_Context *context) { + abort(); +} From 47f13bf921d980738d7538cbe1c18fa9cfe3a9af Mon Sep 17 00:00:00 2001 From: whitequark Date: Mon, 27 Jul 2015 04:44:40 +0300 Subject: [PATCH 150/369] Always load the personality library in JIT testbench, if available. --- artiq/compiler/testbench/jit.py | 12 +++++------- artiq/compiler/transforms/artiq_ir_generator.py | 2 +- artiq/compiler/transforms/llvm_ir_generator.py | 14 ++++++++------ lit-test/test/exceptions/raise.py | 2 +- lit-test/test/integration/arithmetics.py | 1 + lit-test/test/integration/builtin.py | 1 + lit-test/test/integration/list.py | 1 + lit-test/test/integration/subscript.py | 1 + lit-test/test/lit.cfg | 2 +- 9 files changed, 20 insertions(+), 16 deletions(-) diff --git a/artiq/compiler/testbench/jit.py b/artiq/compiler/testbench/jit.py index 6c59b7043..9e6fad53e 100644 --- a/artiq/compiler/testbench/jit.py +++ b/artiq/compiler/testbench/jit.py @@ -1,5 +1,4 @@ -import sys, fileinput -from ctypes import CFUNCTYPE +import os, sys, fileinput, ctypes from pythonparser import diagnostic from llvmlite import binding as llvm from .. import Module @@ -10,10 +9,9 @@ llvm.initialize_native_asmprinter() llvm.check_jit_execution() def main(): - while sys.argv[1].startswith("+"): - if sys.argv[1] == "+load": - llvm.load_library_permanently(sys.argv[2]) - del sys.argv[1:3] + libartiq_personality = os.getenv('LIBARTIQ_PERSONALITY') + if libartiq_personality is not None: + llvm.load_library_permanently(libartiq_personality) def process_diagnostic(diag): print("\n".join(diag.render())) @@ -34,7 +32,7 @@ def main(): lljit = llvm.create_mcjit_compiler(llparsedmod, llmachine) lljit.finalize_object() llmain = lljit.get_pointer_to_global(llparsedmod.get_function(llmod.name + ".__modinit__")) - CFUNCTYPE(None)(llmain)() + ctypes.CFUNCTYPE(None)(llmain)() if __name__ == "__main__": main() diff --git a/artiq/compiler/transforms/artiq_ir_generator.py b/artiq/compiler/transforms/artiq_ir_generator.py index 62f7f995f..50ddffdb6 100644 --- a/artiq/compiler/transforms/artiq_ir_generator.py +++ b/artiq/compiler/transforms/artiq_ir_generator.py @@ -789,7 +789,7 @@ class ARTIQIRGenerator(algorithm.Visitor): lambda: self.alloc_exn(builtins.TValueError(), ir.Constant("slice size {0} is larger than iterable length {1}", builtins.TStr()), - slice_size, iterable_len)) + slice_size, length)) if self.current_assign is None: is_neg_size = self.append(ir.Compare(ast.Lt(loc=None), diff --git a/artiq/compiler/transforms/llvm_ir_generator.py b/artiq/compiler/transforms/llvm_ir_generator.py index a88058b96..9f1aecf59 100644 --- a/artiq/compiler/transforms/llvm_ir_generator.py +++ b/artiq/compiler/transforms/llvm_ir_generator.py @@ -103,12 +103,14 @@ class LLVMIRGenerator: linkage = "private" unnamed_addr = True - llstrty = ll.ArrayType(ll.IntType(8), len(as_bytes)) - llconst = ll.GlobalVariable(self.llmodule, llstrty, name) - llconst.global_constant = True - llconst.initializer = ll.Constant(llstrty, bytearray(as_bytes)) - llconst.linkage = linkage - llconst.unnamed_addr = unnamed_addr + llconst = self.llmodule.get_global(name) + if llconst is None: + llstrty = ll.ArrayType(ll.IntType(8), len(as_bytes)) + llconst = ll.GlobalVariable(self.llmodule, llstrty, name) + llconst.global_constant = True + llconst.initializer = ll.Constant(llstrty, bytearray(as_bytes)) + llconst.linkage = linkage + llconst.unnamed_addr = unnamed_addr return llconst.bitcast(ll.IntType(8).as_pointer()) else: diff --git a/lit-test/test/exceptions/raise.py b/lit-test/test/exceptions/raise.py index 307c50df4..dbb221de4 100644 --- a/lit-test/test/exceptions/raise.py +++ b/lit-test/test/exceptions/raise.py @@ -1,4 +1,4 @@ -# RUN: %python -m artiq.compiler.testbench.jit +load %personality %s +# RUN: %python -m artiq.compiler.testbench.jit %s # REQUIRES: exceptions 1/0 diff --git a/lit-test/test/integration/arithmetics.py b/lit-test/test/integration/arithmetics.py index b59d03c91..1963121e6 100644 --- a/lit-test/test/integration/arithmetics.py +++ b/lit-test/test/integration/arithmetics.py @@ -1,5 +1,6 @@ # RUN: %python -m artiq.compiler.testbench.jit %s # RUN: %python %s +# REQUIRES: exceptions assert -(-1) == 1 assert -(-1.0) == 1.0 diff --git a/lit-test/test/integration/builtin.py b/lit-test/test/integration/builtin.py index 256abe76e..dbb7e471c 100644 --- a/lit-test/test/integration/builtin.py +++ b/lit-test/test/integration/builtin.py @@ -1,5 +1,6 @@ # RUN: %python -m artiq.compiler.testbench.jit %s # RUN: %python %s +# REQUIRES: exceptions assert bool() is False # bool(x) is tested in bool.py diff --git a/lit-test/test/integration/list.py b/lit-test/test/integration/list.py index 660293db7..06f08e426 100644 --- a/lit-test/test/integration/list.py +++ b/lit-test/test/integration/list.py @@ -1,5 +1,6 @@ # RUN: %python -m artiq.compiler.testbench.jit %s # RUN: %python %s +# REQUIRES: exceptions [x, y] = [1, 2] assert (x, y) == (1, 2) diff --git a/lit-test/test/integration/subscript.py b/lit-test/test/integration/subscript.py index 15b3651e8..eaa7f8455 100644 --- a/lit-test/test/integration/subscript.py +++ b/lit-test/test/integration/subscript.py @@ -1,5 +1,6 @@ # RUN: %python -m artiq.compiler.testbench.jit %s # RUN: %python %s +# REQUIRES: exceptions lst = list(range(10)) assert lst[0] == 0 diff --git a/lit-test/test/lit.cfg b/lit-test/test/lit.cfg index 1d7478215..b27244fba 100644 --- a/lit-test/test/lit.cfg +++ b/lit-test/test/lit.cfg @@ -18,6 +18,6 @@ if os.name == 'posix': lit_config.fatal("Unable to build JIT support library") personality_lib = os.path.join(personality_build, 'libartiq_personality.so') - config.substitutions.append( ('%personality', personality_lib) ) + config.environment['LIBARTIQ_PERSONALITY'] = personality_lib config.available_features.add('exceptions') From bb5fe601378bf9432cd2a8c431b6802ec6681ec3 Mon Sep 17 00:00:00 2001 From: whitequark Date: Mon, 27 Jul 2015 05:46:43 +0300 Subject: [PATCH 151/369] Implement exception raising. --- lit-test/libartiq_personality/Makefile | 4 +- .../libartiq_personality/artiq_terminate.c | 14 ++++++ lit-test/not.py | 2 + lit-test/test/exceptions/raise.py | 4 +- lit-test/test/lit.cfg | 4 ++ soc/runtime/artiq_personality.c | 44 +++++++++++++------ soc/runtime/artiq_personality.h | 31 +++++++++++++ 7 files changed, 86 insertions(+), 17 deletions(-) create mode 100644 lit-test/libartiq_personality/artiq_terminate.c create mode 100644 lit-test/not.py create mode 100644 soc/runtime/artiq_personality.h diff --git a/lit-test/libartiq_personality/Makefile b/lit-test/libartiq_personality/Makefile index 3ef94aeb5..47daa9ac1 100644 --- a/lit-test/libartiq_personality/Makefile +++ b/lit-test/libartiq_personality/Makefile @@ -1,4 +1,4 @@ CC ?= clang -libartiq_personality.so: ../../soc/runtime/artiq_personality.c - $(CC) -Wall -Werror -I. -fPIC -shared -o $@ $< +libartiq_personality.so: ../../soc/runtime/artiq_personality.c artiq_terminate.c + $(CC) -Wall -Werror -I. -I../../soc/runtime -fPIC -shared -o $@ $^ diff --git a/lit-test/libartiq_personality/artiq_terminate.c b/lit-test/libartiq_personality/artiq_terminate.c new file mode 100644 index 000000000..9be7f36ca --- /dev/null +++ b/lit-test/libartiq_personality/artiq_terminate.c @@ -0,0 +1,14 @@ +#include +#include +#include +#include +#include + +void __artiq_terminate(struct artiq_exception *exn) { + printf("Uncaught %s: %s (%"PRIi64", %"PRIi64", %"PRIi64")\n" + "at %s:%"PRIi32":%"PRIi32"", + exn->name, exn->message, + exn->param[0], exn->param[1], exn->param[1], + exn->file, exn->line, exn->column + 1); + exit(1); +} diff --git a/lit-test/not.py b/lit-test/not.py new file mode 100644 index 000000000..8c0421623 --- /dev/null +++ b/lit-test/not.py @@ -0,0 +1,2 @@ +import sys, subprocess +exit(not subprocess.call(sys.argv[1:])) diff --git a/lit-test/test/exceptions/raise.py b/lit-test/test/exceptions/raise.py index dbb221de4..4e056e025 100644 --- a/lit-test/test/exceptions/raise.py +++ b/lit-test/test/exceptions/raise.py @@ -1,4 +1,6 @@ -# RUN: %python -m artiq.compiler.testbench.jit %s +# RUN: %not %python -m artiq.compiler.testbench.jit %s # REQUIRES: exceptions +# CHECK-L: Uncaught ZeroDivisionError: cannot divide by zero (0, 0, 0) +# CHECK-L: at input.py:${LINE:+1}:0 1/0 diff --git a/lit-test/test/lit.cfg b/lit-test/test/lit.cfg index b27244fba..9cbc6d070 100644 --- a/lit-test/test/lit.cfg +++ b/lit-test/test/lit.cfg @@ -9,9 +9,13 @@ config.test_format = lit.formats.ShTest() config.suffixes = ['.py'] python_executable = 'python3' + harness = '{} {}'.format(python_executable, os.path.join(root, 'harness.py')) config.substitutions.append( ('%python', harness) ) +not_ = '{} {}'.format(python_executable, os.path.join(root, 'not.py')) +config.substitutions.append( ('%not', not_) ) + if os.name == 'posix': personality_build = os.path.join(root, 'libartiq_personality') if subprocess.call(['make', '-sC', personality_build]) != 0: diff --git a/soc/runtime/artiq_personality.c b/soc/runtime/artiq_personality.c index 831abc82a..5c18de55c 100644 --- a/soc/runtime/artiq_personality.c +++ b/soc/runtime/artiq_personality.c @@ -1,25 +1,41 @@ #include #include +#include #include #include +#include "artiq_personality.h" -struct artiq_exception { - const char *name; - const char *file; - int32_t line; - int32_t column; - const char *message; - int64_t param[3]; +#define ARTIQ_EXCEPTION_CLASS 0x4152545141525451LL // 'ARTQARTQ' + +struct artiq_raised_exception { + struct _Unwind_Exception unwind; + struct artiq_exception artiq; }; -void __artiq_raise(struct artiq_exception *artiq_exn) { - printf("raised %s\n", artiq_exn->name); - abort(); +static void __artiq_cleanup(_Unwind_Reason_Code reason, struct _Unwind_Exception *exc) { + struct artiq_raised_exception *inflight = (struct artiq_raised_exception*) exc; + // The in-flight exception is statically allocated, so we don't need to free it. + // But, we clear it to mark it as processed. + memset(&inflight->artiq, 0, sizeof(struct artiq_exception)); } -_Unwind_Reason_Code __artiq_personality(int version, - _Unwind_Action actions, uint64_t exceptionClass, - struct _Unwind_Exception *exceptionObject, - struct _Unwind_Context *context) { +void __artiq_raise(struct artiq_exception *artiq_exn) { + static struct artiq_raised_exception inflight; + memcpy(&inflight.artiq, artiq_exn, sizeof(struct artiq_exception)); + inflight.unwind.exception_class = ARTIQ_EXCEPTION_CLASS; + inflight.unwind.exception_cleanup = &__artiq_cleanup; + + _Unwind_Reason_Code result = _Unwind_RaiseException(&inflight.unwind); + if(result == _URC_END_OF_STACK) { + __artiq_terminate(&inflight.artiq); + } else { + fprintf(stderr, "__artiq_raise: unexpected error (%d)\n", result); + abort(); + } +} + +_Unwind_Reason_Code __artiq_personality( + int version, _Unwind_Action actions, uint64_t exceptionClass, + struct _Unwind_Exception *exceptionObject, struct _Unwind_Context *context) { abort(); } diff --git a/soc/runtime/artiq_personality.h b/soc/runtime/artiq_personality.h new file mode 100644 index 000000000..9c874e516 --- /dev/null +++ b/soc/runtime/artiq_personality.h @@ -0,0 +1,31 @@ +#ifndef __ARTIQ_PERSONALITY_H +#define __ARTIQ_PERSONALITY_H + +#include + +struct artiq_exception { + union { + void *typeinfo; + const char *name; + }; + const char *file; + int32_t line; + int32_t column; + const char *message; + int64_t param[3]; +}; + +#ifdef __cplusplus +extern "C" { +#endif + +void __artiq_terminate(struct artiq_exception *artiq_exn) + __attribute__((noreturn)); + +void __artiq_raise(struct artiq_exception *artiq_exn); + +#ifdef __cplusplus +} +#endif + +#endif /* __ARTIQ_PERSONALITY_H */ From 7c77dd317aa7ba0e60aed2793b1c240ca77f921f Mon Sep 17 00:00:00 2001 From: whitequark Date: Mon, 27 Jul 2015 09:10:20 +0300 Subject: [PATCH 152/369] Implement __artiq_personality. --- artiq/compiler/ir.py | 13 +- .../compiler/transforms/artiq_ir_generator.py | 5 +- .../compiler/transforms/llvm_ir_generator.py | 25 +- lit-test/libartiq_personality/Makefile | 2 +- .../libartiq_personality/artiq_terminate.c | 2 +- lit-test/test/exceptions/catch.py | 8 + soc/runtime/artiq_personality.c | 329 +++++++++++++++++- soc/runtime/artiq_personality.h | 2 +- 8 files changed, 373 insertions(+), 13 deletions(-) create mode 100644 lit-test/test/exceptions/catch.py diff --git a/artiq/compiler/ir.py b/artiq/compiler/ir.py index 8e042e14e..94679fd18 100644 --- a/artiq/compiler/ir.py +++ b/artiq/compiler/ir.py @@ -979,10 +979,16 @@ class Raise(Terminator): """ :param value: (:class:`Value`) exception value + :param exn: (:class:`BasicBlock`) exceptional target """ - def __init__(self, value, name=""): + def __init__(self, value, exn=None, name=""): assert isinstance(value, Value) - super().__init__([value], builtins.TNone(), name) + if exn is not None: + assert isinstance(exn, BasicBlock) + operands = [value, exn] + else: + operands = [value] + super().__init__(operands, builtins.TNone(), name) def opcode(self): return "raise" @@ -990,6 +996,9 @@ class Raise(Terminator): def value(self): return self.operands[0] + def exception_target(self): + return self.operands[1] + class Invoke(Terminator): """ A function call operation that supports exception handling. diff --git a/artiq/compiler/transforms/artiq_ir_generator.py b/artiq/compiler/transforms/artiq_ir_generator.py index 50ddffdb6..54b1efecf 100644 --- a/artiq/compiler/transforms/artiq_ir_generator.py +++ b/artiq/compiler/transforms/artiq_ir_generator.py @@ -472,7 +472,10 @@ class ARTIQIRGenerator(algorithm.Visitor): 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.Raise(exn)) + if self.unwind_target: + self.append(ir.Raise(exn, self.unwind_target)) + else: + self.append(ir.Raise(exn)) def visit_Raise(self, node): self.raise_exn(self.visit(node.exc)) diff --git a/artiq/compiler/transforms/llvm_ir_generator.py b/artiq/compiler/transforms/llvm_ir_generator.py index 9f1aecf59..965ddad8c 100644 --- a/artiq/compiler/transforms/llvm_ir_generator.py +++ b/artiq/compiler/transforms/llvm_ir_generator.py @@ -582,17 +582,30 @@ class LLVMIRGenerator: return self.llbuilder.unreachable() def process_Raise(self, insn): - arg = self.map(insn.operands[0]) - llinsn = self.llbuilder.call(self.llbuiltin("__artiq_raise"), [arg], - name=insn.name) + llexn = self.map(insn.value()) + 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(self.llbuiltin("__artiq_raise"), [llexn], + llnormalblock, llunwindblock, + name=insn.name) + else: + llinsn = self.llbuilder.call(self.llbuiltin("__artiq_raise"), [llexn], + name=insn.name) + self.llbuilder.unreachable() llinsn.attributes.add('noreturn') - self.llbuilder.unreachable() return llinsn def process_LandingPad(self, insn): - lllandingpad = self.llbuilder.landingpad(ll.LiteralStructType([ll.IntType(8).as_pointer()]), + # Layout on return from landing pad: {%_Unwind_Exception*, %Exception*} + lllandingpadty = ll.LiteralStructType([ll.IntType(8).as_pointer(), + ll.IntType(8).as_pointer()]) + lllandingpad = self.llbuilder.landingpad(lllandingpadty, self.llbuiltin("__artiq_personality")) - llrawexn = self.llbuilder.extract_value(lllandingpad, 0) + 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)]) llexnname = self.llbuilder.load(llexnnameptr) diff --git a/lit-test/libartiq_personality/Makefile b/lit-test/libartiq_personality/Makefile index 47daa9ac1..4fa32ab96 100644 --- a/lit-test/libartiq_personality/Makefile +++ b/lit-test/libartiq_personality/Makefile @@ -1,4 +1,4 @@ CC ?= clang libartiq_personality.so: ../../soc/runtime/artiq_personality.c artiq_terminate.c - $(CC) -Wall -Werror -I. -I../../soc/runtime -fPIC -shared -o $@ $^ + $(CC) -Wall -Werror -I. -I../../soc/runtime -g -fPIC -shared -o $@ $^ diff --git a/lit-test/libartiq_personality/artiq_terminate.c b/lit-test/libartiq_personality/artiq_terminate.c index 9be7f36ca..c4c2bf7db 100644 --- a/lit-test/libartiq_personality/artiq_terminate.c +++ b/lit-test/libartiq_personality/artiq_terminate.c @@ -6,7 +6,7 @@ void __artiq_terminate(struct artiq_exception *exn) { printf("Uncaught %s: %s (%"PRIi64", %"PRIi64", %"PRIi64")\n" - "at %s:%"PRIi32":%"PRIi32"", + "at %s:%"PRIi32":%"PRIi32"\n", exn->name, exn->message, exn->param[0], exn->param[1], exn->param[1], exn->file, exn->line, exn->column + 1); diff --git a/lit-test/test/exceptions/catch.py b/lit-test/test/exceptions/catch.py new file mode 100644 index 000000000..4d2730859 --- /dev/null +++ b/lit-test/test/exceptions/catch.py @@ -0,0 +1,8 @@ +# RUN: %python -m artiq.compiler.testbench.jit %s +# REQUIRES: exceptions + +try: + 1/0 +except ZeroDivisionError: + # CHECK-L: OK + print("OK") diff --git a/soc/runtime/artiq_personality.c b/soc/runtime/artiq_personality.c index 5c18de55c..7057bde17 100644 --- a/soc/runtime/artiq_personality.c +++ b/soc/runtime/artiq_personality.c @@ -5,6 +5,198 @@ #include #include "artiq_personality.h" +/* Logging */ + +#ifndef NDEBUG +#define EH_LOG0(fmt) fprintf(stderr, "__artiq_personality: " fmt "\n") +#define EH_LOG(fmt, ...) fprintf(stderr, "__artiq_personality: " fmt "\n", __VA_ARGS__) +#else +#define EH_LOG0(fmt) +#define EH_LOG(fmt, ...) +#endif + +#define EH_FAIL(err) \ + do { \ + fprintf(stderr, "__artiq_personality fatal: %s\n", err); \ + abort(); \ + } while(0) + +#define EH_ASSERT(expr) \ + if(!(expr)) EH_FAIL(#expr) + +/* DWARF format handling */ + +enum { + DW_EH_PE_absptr = 0x00, + DW_EH_PE_uleb128 = 0x01, + DW_EH_PE_udata2 = 0x02, + DW_EH_PE_udata4 = 0x03, + DW_EH_PE_udata8 = 0x04, + DW_EH_PE_sleb128 = 0x09, + DW_EH_PE_sdata2 = 0x0A, + DW_EH_PE_sdata4 = 0x0B, + DW_EH_PE_sdata8 = 0x0C, + DW_EH_PE_pcrel = 0x10, + DW_EH_PE_textrel = 0x20, + DW_EH_PE_datarel = 0x30, + DW_EH_PE_funcrel = 0x40, + DW_EH_PE_aligned = 0x50, + DW_EH_PE_indirect = 0x80, + DW_EH_PE_omit = 0xFF +}; + +// Read a uleb128 encoded value and advance pointer +// See Variable Length Data in: http://dwarfstd.org/Dwarf3.pdf +static uintptr_t readULEB128(const uint8_t **data) { + uintptr_t result = 0; + uintptr_t shift = 0; + unsigned char byte; + const uint8_t *p = *data; + + do { + byte = *p++; + result |= (byte & 0x7f) << shift; + shift += 7; + } + while (byte & 0x80); + + *data = p; + + return result; +} + +// Read a sleb128 encoded value and advance pointer +// See Variable Length Data in: http://dwarfstd.org/Dwarf3.pdf +static uintptr_t readSLEB128(const uint8_t **data) { + uintptr_t result = 0; + uintptr_t shift = 0; + unsigned char byte; + const uint8_t *p = *data; + + do { + byte = *p++; + result |= (byte & 0x7f) << shift; + shift += 7; + } + while (byte & 0x80); + + *data = p; + + if ((byte & 0x40) && (shift < (sizeof(result) << 3))) { + result |= (~0 << shift); + } + + return result; +} + +static unsigned getEncodingSize(uint8_t Encoding) { + if (Encoding == DW_EH_PE_omit) + return 0; + + switch (Encoding & 0x0F) { + case DW_EH_PE_absptr: + return sizeof(uintptr_t); + case DW_EH_PE_udata2: + return sizeof(uint16_t); + case DW_EH_PE_udata4: + return sizeof(uint32_t); + case DW_EH_PE_udata8: + return sizeof(uint64_t); + case DW_EH_PE_sdata2: + return sizeof(int16_t); + case DW_EH_PE_sdata4: + return sizeof(int32_t); + case DW_EH_PE_sdata8: + return sizeof(int64_t); + default: + // not supported + abort(); + } +} + +// Read a pointer encoded value and advance pointer +// See Variable Length Data in: http://dwarfstd.org/Dwarf3.pdf +static uintptr_t readEncodedPointer(const uint8_t **data, uint8_t encoding) { + uintptr_t result = 0; + const uint8_t *p = *data; + + if (encoding == DW_EH_PE_omit) + return(result); + + // first get value + switch (encoding & 0x0F) { + case DW_EH_PE_absptr: + result = *((uintptr_t*)p); + p += sizeof(uintptr_t); + break; + case DW_EH_PE_uleb128: + result = readULEB128(&p); + break; + // Note: This case has not been tested + case DW_EH_PE_sleb128: + result = readSLEB128(&p); + break; + case DW_EH_PE_udata2: + result = *((uint16_t*)p); + p += sizeof(uint16_t); + break; + case DW_EH_PE_udata4: + result = *((uint32_t*)p); + p += sizeof(uint32_t); + break; + case DW_EH_PE_udata8: + result = *((uint64_t*)p); + p += sizeof(uint64_t); + break; + case DW_EH_PE_sdata2: + result = *((int16_t*)p); + p += sizeof(int16_t); + break; + case DW_EH_PE_sdata4: + result = *((int32_t*)p); + p += sizeof(int32_t); + break; + case DW_EH_PE_sdata8: + result = *((int64_t*)p); + p += sizeof(int64_t); + break; + default: + // not supported + abort(); + break; + } + + // then add relative offset + switch (encoding & 0x70) { + case DW_EH_PE_absptr: + // do nothing + break; + case DW_EH_PE_pcrel: + result += (uintptr_t)(*data); + break; + case DW_EH_PE_textrel: + case DW_EH_PE_datarel: + case DW_EH_PE_funcrel: + case DW_EH_PE_aligned: + default: + // not supported + abort(); + break; + } + + // then apply indirection + if (encoding & DW_EH_PE_indirect) { + result = *((uintptr_t*)result); + } + + *data = p; + + return result; +} + + +/* Raising and catching */ + #define ARTIQ_EXCEPTION_CLASS 0x4152545141525451LL // 'ARTQARTQ' struct artiq_raised_exception { @@ -37,5 +229,140 @@ void __artiq_raise(struct artiq_exception *artiq_exn) { _Unwind_Reason_Code __artiq_personality( int version, _Unwind_Action actions, uint64_t exceptionClass, struct _Unwind_Exception *exceptionObject, struct _Unwind_Context *context) { - abort(); + EH_LOG("entry (actions =%s%s%s%s; class=%08lx; object=%p, context=%p)", + (actions & _UA_SEARCH_PHASE ? " search" : ""), + (actions & _UA_CLEANUP_PHASE ? " cleanup" : ""), + (actions & _UA_HANDLER_FRAME ? " handler" : ""), + (actions & _UA_FORCE_UNWIND ? " force-unwind" : ""), + exceptionClass, exceptionObject, context); + EH_ASSERT((exceptionClass == ARTIQ_EXCEPTION_CLASS) && + "Foreign exceptions are not supported"); + + struct artiq_raised_exception *inflight = + (struct artiq_raised_exception*)exceptionObject; + EH_LOG("exception name=%s", + inflight->artiq.name); + + // Get a pointer to LSDA. If there's no LSDA, this function doesn't + // actually handle any exceptions. + const uint8_t *lsda = (const uint8_t*) _Unwind_GetLanguageSpecificData(context); + if(lsda == NULL) + return _URC_CONTINUE_UNWIND; + + EH_LOG("lsda=%p", lsda); + + // Get the current instruction pointer and offset it before next + // instruction in the current frame which threw the exception. + uintptr_t pc = _Unwind_GetIP(context) - 1; + + // Get beginning of the current frame's code. + uintptr_t funcStart = _Unwind_GetRegionStart(context); + uintptr_t pcOffset = pc - funcStart; + + EH_LOG("pc=%p (%p+%p)", (void*)pc, (void*)funcStart, (void*)pcOffset); + + // Parse LSDA header. + uint8_t lpStartEncoding = *lsda++; + if (lpStartEncoding != DW_EH_PE_omit) { + readEncodedPointer(&lsda, lpStartEncoding); + } + + uint8_t ttypeEncoding = *lsda++; + const uint8_t *classInfo = NULL; + if (ttypeEncoding != DW_EH_PE_omit) { + // Calculate type info locations in emitted dwarf code which + // were flagged by type info arguments to llvm.eh.selector + // intrinsic + uintptr_t classInfoOffset = readULEB128(&lsda); + classInfo = lsda + classInfoOffset; + EH_LOG("classInfo=%p", classInfo); + } + + // Walk call-site table looking for range that includes current PC. + uint8_t callSiteEncoding = *lsda++; + uint32_t callSiteTableLength = readULEB128(&lsda); + const uint8_t *callSiteTableStart = lsda; + const uint8_t *callSiteTableEnd = callSiteTableStart + callSiteTableLength; + const uint8_t *actionTableStart = callSiteTableEnd; + const uint8_t *callSitePtr = callSiteTableStart; + + while (callSitePtr < callSiteTableEnd) { + uintptr_t start = readEncodedPointer(&callSitePtr, + callSiteEncoding); + uintptr_t length = readEncodedPointer(&callSitePtr, + callSiteEncoding); + uintptr_t landingPad = readEncodedPointer(&callSitePtr, + callSiteEncoding); + uintptr_t actionValue = readULEB128(&callSitePtr); + + EH_LOG("call site (start=+%p, len=%d, landingPad=+%p, actionValue=%d)", + (void*)start, (int)length, (void*)landingPad, (int)actionValue); + + if(landingPad == 0) { + EH_LOG0("no landing pad, skipping"); + continue; + } + + if ((start <= pcOffset) && (pcOffset < (start + length))) { + EH_LOG0("call site matches pc"); + + int exceptionMatched = 0; + if(actionValue) { + const uint8_t *actionEntry = actionTableStart + (actionValue - 1); + EH_LOG("actionEntry=%p", actionEntry); + + for(;;) { + // Each emitted DWARF action corresponds to a 2 tuple of + // type info address offset, and action offset to the next + // emitted action. + intptr_t typeInfoOffset = readSLEB128(&actionEntry); + const uint8_t *tempActionEntry = actionEntry; + intptr_t actionOffset = readSLEB128(&tempActionEntry); + EH_LOG("typeInfoOffset=%p actionOffset=%p", + (void*)typeInfoOffset, (void*)actionOffset); + EH_ASSERT((typeInfoOffset >= 0) && "Filter clauses are not supported"); + + unsigned encodingSize = getEncodingSize(ttypeEncoding); + const uint8_t *typeInfoPtrPtr = classInfo - typeInfoOffset * encodingSize; + uintptr_t typeInfoPtr = readEncodedPointer(&typeInfoPtrPtr, ttypeEncoding); + EH_LOG("encodingSize=%u typeInfoPtrPtr=%p typeInfoPtr=%p", + encodingSize, typeInfoPtrPtr, (void*)typeInfoPtr); + EH_LOG("typeInfo=%s", (char*)typeInfoPtr); + + if(inflight->artiq.typeinfo == typeInfoPtr) { + EH_LOG0("matching action found"); + exceptionMatched = 1; + break; + } + + if (!actionOffset) + break; + + actionEntry += actionOffset; + } + } + + if (!(actions & _UA_SEARCH_PHASE)) { + EH_LOG0("jumping to landing pad"); + + _Unwind_SetGR(context, __builtin_eh_return_data_regno(0), + (uintptr_t)exceptionObject); + _Unwind_SetGR(context, __builtin_eh_return_data_regno(1), + (uintptr_t)&inflight->artiq); + _Unwind_SetIP(context, funcStart + landingPad); + + return _URC_INSTALL_CONTEXT; + } else if(exceptionMatched) { + EH_LOG0("handler found"); + + return _URC_HANDLER_FOUND; + } else { + EH_LOG0("handler not found"); + + return _URC_CONTINUE_UNWIND; + } + } + } + + return _URC_CONTINUE_UNWIND; } diff --git a/soc/runtime/artiq_personality.h b/soc/runtime/artiq_personality.h index 9c874e516..45b0ef9f1 100644 --- a/soc/runtime/artiq_personality.h +++ b/soc/runtime/artiq_personality.h @@ -5,7 +5,7 @@ struct artiq_exception { union { - void *typeinfo; + uintptr_t typeinfo; const char *name; }; const char *file; From 90be44c596d97c60853c6104f8d476be15e9fac4 Mon Sep 17 00:00:00 2001 From: whitequark Date: Mon, 27 Jul 2015 10:13:22 +0300 Subject: [PATCH 153/369] Add tests for non-exceptional control flow across finally. --- artiq/compiler/ir.py | 5 +- .../transforms/dead_code_eliminator.py | 4 ++ lit-test/test/integration/finally.py | 64 +++++++++++++++++++ 3 files changed, 71 insertions(+), 2 deletions(-) create mode 100644 lit-test/test/integration/finally.py diff --git a/artiq/compiler/ir.py b/artiq/compiler/ir.py index 94679fd18..3a00c7335 100644 --- a/artiq/compiler/ir.py +++ b/artiq/compiler/ir.py @@ -292,7 +292,7 @@ class BasicBlock(NamedValue): def erase(self): # self.instructions is updated while iterating - for insn in list(self.instructions): + for insn in reversed(self.instructions): insn.erase() self.remove_from_parent() # Check this after erasing instructions in case the block @@ -997,7 +997,8 @@ class Raise(Terminator): return self.operands[0] def exception_target(self): - return self.operands[1] + if len(self.operands) > 1: + return self.operands[1] class Invoke(Terminator): """ diff --git a/artiq/compiler/transforms/dead_code_eliminator.py b/artiq/compiler/transforms/dead_code_eliminator.py index 1d4e08f6f..003b8561c 100644 --- a/artiq/compiler/transforms/dead_code_eliminator.py +++ b/artiq/compiler/transforms/dead_code_eliminator.py @@ -25,6 +25,10 @@ class DeadCodeEliminator: use.remove_incoming_block(block) if not any(use.operands): self.remove_instruction(use) + elif isinstance(use, ir.SetLocal): + # Setting the target for `finally` resumption, e.g. + # setlocal(.k) %v.4, label %try.doreturn + use.erase() else: assert False diff --git a/lit-test/test/integration/finally.py b/lit-test/test/integration/finally.py new file mode 100644 index 000000000..376051e66 --- /dev/null +++ b/lit-test/test/integration/finally.py @@ -0,0 +1,64 @@ +# RUN: %python -m artiq.compiler.testbench.jit %s +# RUN: %python %s +# REQUIRES: exceptions + +def f(): + while True: + try: + print("f-try") + break + finally: + print("f-finally") + print("f-out") + +# CHECK-L: f-try +# CHECK-L: f-finally +# CHECK-L: f-out +f() + +def g(): + x = True + while x: + try: + print("g-try") + x = False + continue + finally: + print("g-finally") + print("g-out") + +# CHECK-L: g-try +# CHECK-L: g-finally +# CHECK-L: g-out +g() + +def h(): + try: + print("h-try") + return 10 + finally: + print("h-finally") + print("h-out") + return 20 + +# CHECK-L: h-try +# CHECK-L: h-finally +# CHECK-NOT-L: h-out +# CHECK-L: h 10 +print("h", h()) + +def i(): + try: + print("i-try") + return 10 + finally: + print("i-finally") + return 30 + print("i-out") + return 20 + +# CHECK-L: i-try +# CHECK-L: i-finally +# CHECK-NOT-L: i-out +# CHECK-L: i 30 +print("i", i()) From a83e7e224819f4a1235c80531fec7b95211dbc70 Mon Sep 17 00:00:00 2001 From: whitequark Date: Mon, 27 Jul 2015 10:13:57 +0300 Subject: [PATCH 154/369] Add tests for exceptional control flow. --- .../compiler/transforms/artiq_ir_generator.py | 22 +++++++++---------- lit-test/test/exceptions/catch_all.py | 13 +++++++++++ lit-test/test/exceptions/catch_multi.py | 15 +++++++++++++ lit-test/test/exceptions/catch_outer.py | 15 +++++++++++++ .../test/exceptions/{raise.py => uncaught.py} | 0 soc/runtime/artiq_personality.c | 22 +++++++++---------- 6 files changed, 65 insertions(+), 22 deletions(-) create mode 100644 lit-test/test/exceptions/catch_all.py create mode 100644 lit-test/test/exceptions/catch_multi.py create mode 100644 lit-test/test/exceptions/catch_outer.py rename lit-test/test/exceptions/{raise.py => uncaught.py} (100%) diff --git a/artiq/compiler/transforms/artiq_ir_generator.py b/artiq/compiler/transforms/artiq_ir_generator.py index 54b1efecf..ef3c44166 100644 --- a/artiq/compiler/transforms/artiq_ir_generator.py +++ b/artiq/compiler/transforms/artiq_ir_generator.py @@ -546,13 +546,13 @@ class ARTIQIRGenerator(algorithm.Visitor): has_catchall = True self.current_block = handler - handlers.append(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): finalizer = self.add_block("finally") @@ -580,12 +580,12 @@ class ARTIQIRGenerator(algorithm.Visitor): # to execute. handler = self.add_block("handler.catchall") landingpad.add_clause(handler, None) - handlers.append(handler) + handlers.append((handler, handler)) - for handler in handlers: - if not handler.is_terminated(): - handler.append(ir.SetLocal(final_state, ".k", tail)) - handler.append(ir.Branch(tail)) + for handler, post_handler in handlers: + if not post_handler.is_terminated(): + post_handler.append(ir.SetLocal(final_state, ".k", tail)) + post_handler.append(ir.Branch(tail)) if not post_finalizer.is_terminated(): dest = post_finalizer.append(ir.GetLocal(final_state, ".k")) @@ -594,9 +594,9 @@ class ARTIQIRGenerator(algorithm.Visitor): if not body.is_terminated(): body.append(ir.Branch(tail)) - for handler in handlers: - if not handler.is_terminated(): - handler.append(ir.Branch(tail)) + for handler, post_handler in handlers: + if not post_handler.is_terminated(): + post_handler.append(ir.Branch(tail)) # TODO: With diff --git a/lit-test/test/exceptions/catch_all.py b/lit-test/test/exceptions/catch_all.py new file mode 100644 index 000000000..21d8f9581 --- /dev/null +++ b/lit-test/test/exceptions/catch_all.py @@ -0,0 +1,13 @@ +# RUN: %python -m artiq.compiler.testbench.jit %s +# 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/lit-test/test/exceptions/catch_multi.py b/lit-test/test/exceptions/catch_multi.py new file mode 100644 index 000000000..fc836e9a0 --- /dev/null +++ b/lit-test/test/exceptions/catch_multi.py @@ -0,0 +1,15 @@ +# RUN: %python -m artiq.compiler.testbench.jit %s +# 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/lit-test/test/exceptions/catch_outer.py b/lit-test/test/exceptions/catch_outer.py new file mode 100644 index 000000000..0011f5c1b --- /dev/null +++ b/lit-test/test/exceptions/catch_outer.py @@ -0,0 +1,15 @@ +# RUN: %python -m artiq.compiler.testbench.jit %s +# 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/lit-test/test/exceptions/raise.py b/lit-test/test/exceptions/uncaught.py similarity index 100% rename from lit-test/test/exceptions/raise.py rename to lit-test/test/exceptions/uncaught.py diff --git a/soc/runtime/artiq_personality.c b/soc/runtime/artiq_personality.c index 7057bde17..b7bbad460 100644 --- a/soc/runtime/artiq_personality.c +++ b/soc/runtime/artiq_personality.c @@ -229,7 +229,7 @@ void __artiq_raise(struct artiq_exception *artiq_exn) { _Unwind_Reason_Code __artiq_personality( int version, _Unwind_Action actions, uint64_t exceptionClass, struct _Unwind_Exception *exceptionObject, struct _Unwind_Context *context) { - EH_LOG("entry (actions =%s%s%s%s; class=%08lx; object=%p, context=%p)", + EH_LOG("===> entry (actions =%s%s%s%s; class=%08lx; object=%p, context=%p)", (actions & _UA_SEARCH_PHASE ? " search" : ""), (actions & _UA_CLEANUP_PHASE ? " cleanup" : ""), (actions & _UA_HANDLER_FRAME ? " handler" : ""), @@ -240,7 +240,7 @@ _Unwind_Reason_Code __artiq_personality( struct artiq_raised_exception *inflight = (struct artiq_raised_exception*)exceptionObject; - EH_LOG("exception name=%s", + EH_LOG("=> exception name=%s", inflight->artiq.name); // Get a pointer to LSDA. If there's no LSDA, this function doesn't @@ -259,7 +259,7 @@ _Unwind_Reason_Code __artiq_personality( uintptr_t funcStart = _Unwind_GetRegionStart(context); uintptr_t pcOffset = pc - funcStart; - EH_LOG("pc=%p (%p+%p)", (void*)pc, (void*)funcStart, (void*)pcOffset); + EH_LOG("=> pc=%p (%p+%p)", (void*)pc, (void*)funcStart, (void*)pcOffset); // Parse LSDA header. uint8_t lpStartEncoding = *lsda++; @@ -286,7 +286,7 @@ _Unwind_Reason_Code __artiq_personality( const uint8_t *actionTableStart = callSiteTableEnd; const uint8_t *callSitePtr = callSiteTableStart; - while (callSitePtr < callSiteTableEnd) { + while(callSitePtr < callSiteTableEnd) { uintptr_t start = readEncodedPointer(&callSitePtr, callSiteEncoding); uintptr_t length = readEncodedPointer(&callSitePtr, @@ -303,8 +303,8 @@ _Unwind_Reason_Code __artiq_personality( continue; } - if ((start <= pcOffset) && (pcOffset < (start + length))) { - EH_LOG0("call site matches pc"); + if((start <= pcOffset) && (pcOffset < (start + length))) { + EH_LOG0("=> call site matches pc"); int exceptionMatched = 0; if(actionValue) { @@ -329,7 +329,7 @@ _Unwind_Reason_Code __artiq_personality( encodingSize, typeInfoPtrPtr, (void*)typeInfoPtr); EH_LOG("typeInfo=%s", (char*)typeInfoPtr); - if(inflight->artiq.typeinfo == typeInfoPtr) { + if(typeInfoPtr == 0 || inflight->artiq.typeinfo == typeInfoPtr) { EH_LOG0("matching action found"); exceptionMatched = 1; break; @@ -342,8 +342,8 @@ _Unwind_Reason_Code __artiq_personality( } } - if (!(actions & _UA_SEARCH_PHASE)) { - EH_LOG0("jumping to landing pad"); + if(!(actions & _UA_SEARCH_PHASE)) { + EH_LOG0("=> jumping to landing pad"); _Unwind_SetGR(context, __builtin_eh_return_data_regno(0), (uintptr_t)exceptionObject); @@ -353,11 +353,11 @@ _Unwind_Reason_Code __artiq_personality( return _URC_INSTALL_CONTEXT; } else if(exceptionMatched) { - EH_LOG0("handler found"); + EH_LOG0("=> handler found"); return _URC_HANDLER_FOUND; } else { - EH_LOG0("handler not found"); + EH_LOG0("=> handler not found"); return _URC_CONTINUE_UNWIND; } From 2939d4f0f3bb2a20bd34f954ec3f31a07b05ed56 Mon Sep 17 00:00:00 2001 From: whitequark Date: Mon, 27 Jul 2015 12:36:21 +0300 Subject: [PATCH 155/369] Add tests for finally clause and reraising. --- artiq/compiler/ir.py | 50 ++++++++++---- .../compiler/transforms/artiq_ir_generator.py | 47 ++++++++------ .../transforms/dead_code_eliminator.py | 8 +-- artiq/compiler/transforms/inferencer.py | 15 +++-- .../compiler/transforms/llvm_ir_generator.py | 24 +++++-- lit-test/test/exceptions/catch.py | 3 +- lit-test/test/exceptions/catch_all.py | 3 +- lit-test/test/exceptions/catch_multi.py | 3 +- lit-test/test/exceptions/catch_outer.py | 3 +- lit-test/test/exceptions/finally.py | 21 ++++++ lit-test/test/exceptions/finally_catch.py | 17 +++++ lit-test/test/exceptions/finally_raise.py | 23 +++++++ lit-test/test/exceptions/finally_squash.py | 21 ++++++ lit-test/test/exceptions/reraise.py | 16 +++++ lit-test/test/exceptions/reraise_update.py | 16 +++++ lit-test/test/exceptions/uncaught.py | 5 +- lit-test/test/integration/finally.py | 12 ++++ soc/runtime/artiq_personality.c | 65 +++++++++++++------ soc/runtime/artiq_personality.h | 9 ++- t.py | 0 20 files changed, 285 insertions(+), 76 deletions(-) create mode 100644 lit-test/test/exceptions/finally.py create mode 100644 lit-test/test/exceptions/finally_catch.py create mode 100644 lit-test/test/exceptions/finally_raise.py create mode 100644 lit-test/test/exceptions/finally_squash.py create mode 100644 lit-test/test/exceptions/reraise.py create mode 100644 lit-test/test/exceptions/reraise_update.py create mode 100644 t.py diff --git a/artiq/compiler/ir.py b/artiq/compiler/ir.py index 3a00c7335..7cb73ed30 100644 --- a/artiq/compiler/ir.py +++ b/artiq/compiler/ir.py @@ -979,15 +979,14 @@ class Raise(Terminator): """ :param value: (:class:`Value`) exception value - :param exn: (:class:`BasicBlock`) exceptional target + :param exn: (:class:`BasicBlock` or None) exceptional target """ - def __init__(self, value, exn=None, name=""): + def __init__(self, value=None, exn=None, name=""): assert isinstance(value, Value) + operands = [value] if exn is not None: assert isinstance(exn, BasicBlock) - operands = [value, exn] - else: - operands = [value] + operands.append(exn) super().__init__(operands, builtins.TNone(), name) def opcode(self): @@ -1000,6 +999,28 @@ class Raise(Terminator): 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. @@ -1051,12 +1072,18 @@ class LandingPad(Terminator): exception types corresponding to the basic block operands """ - def __init__(self, name=""): - super().__init__([], builtins.TException(), name) + def __init__(self, cleanup, name=""): + super().__init__([cleanup], builtins.TException(), name) self.types = [] + def opcode(self): + return "landingpad" + + def cleanup(self): + return self.operands[0] + def clauses(self): - return zip(self.operands, self.types) + return zip(self.operands[1:], self.types) def add_clause(self, target, typ): assert isinstance(target, BasicBlock) @@ -1065,14 +1092,11 @@ class LandingPad(Terminator): self.types.append(typ.find() if typ is not None else None) target.uses.add(self) - def opcode(self): - return "landingpad" - def _operands_as_string(self): table = [] - for typ, target in zip(self.types, self.operands): + for target, typ in self.clauses(): if typ is None: table.append("... => {}".format(target.as_operand())) else: table.append("{} => {}".format(types.TypePrinter().name(typ), target.as_operand())) - return "[{}]".format(", ".join(table)) + return "cleanup {}, [{}]".format(self.cleanup().as_operand(), ", ".join(table)) diff --git a/artiq/compiler/transforms/artiq_ir_generator.py b/artiq/compiler/transforms/artiq_ir_generator.py index ef3c44166..8c649a25b 100644 --- a/artiq/compiler/transforms/artiq_ir_generator.py +++ b/artiq/compiler/transforms/artiq_ir_generator.py @@ -466,23 +466,29 @@ class ARTIQIRGenerator(algorithm.Visitor): self.append(ir.Branch(self.continue_target)) def raise_exn(self, exn): - loc_file = ir.Constant(self.current_loc.source_buffer.name, builtins.TStr()) - loc_line = ir.Constant(self.current_loc.line(), builtins.TInt(types.TValue(32))) - loc_column = ir.Constant(self.current_loc.column(), builtins.TInt(types.TValue(32))) - self.append(ir.SetAttr(exn, "__file__", loc_file)) - self.append(ir.SetAttr(exn, "__line__", loc_line)) - self.append(ir.SetAttr(exn, "__col__", loc_column)) - if self.unwind_target: - self.append(ir.Raise(exn, self.unwind_target)) + if exn is not None: + loc_file = ir.Constant(self.current_loc.source_buffer.name, builtins.TStr()) + loc_line = ir.Constant(self.current_loc.line(), builtins.TInt(types.TValue(32))) + loc_column = ir.Constant(self.current_loc.column(), builtins.TInt(types.TValue(32))) + self.append(ir.SetAttr(exn, "__file__", loc_file)) + self.append(ir.SetAttr(exn, "__line__", loc_line)) + self.append(ir.SetAttr(exn, "__col__", loc_column)) + + if self.unwind_target is not None: + self.append(ir.Raise(exn, self.unwind_target)) + else: + self.append(ir.Raise(exn)) else: - self.append(ir.Raise(exn)) + if self.unwind_target is not None: + self.append(ir.Reraise(self.unwind_target)) + else: + self.append(ir.Reraise()) def visit_Raise(self, node): self.raise_exn(self.visit(node.exc)) def visit_Try(self, node): dispatcher = self.add_block("try.dispatch") - landingpad = dispatcher.append(ir.LandingPad()) if any(node.finalbody): # k for continuation @@ -532,8 +538,10 @@ class ARTIQIRGenerator(algorithm.Visitor): self.continue_target = old_continue self.return_target = old_return + cleanup = self.add_block('handler.cleanup') + landingpad = dispatcher.append(ir.LandingPad(cleanup)) + handlers = [] - has_catchall = False for handler_node in node.handlers: exn_type = handler_node.name_type.find() if handler_node.filter is not None and \ @@ -543,7 +551,6 @@ class ARTIQIRGenerator(algorithm.Visitor): else: handler = self.add_block("handler.catchall") landingpad.add_clause(handler, None) - has_catchall = True self.current_block = handler if handler_node.name is not None: @@ -561,9 +568,13 @@ class ARTIQIRGenerator(algorithm.Visitor): self.visit(node.finalbody) post_finalizer = self.current_block + reraise = self.add_block('try.reraise') + reraise.append(ir.Reraise(self.unwind_target)) + self.current_block = tail = self.add_block("try.tail") if any(node.finalbody): final_targets.append(tail) + final_targets.append(reraise) if self.break_target: break_proxy.append(ir.Branch(finalizer)) @@ -575,17 +586,13 @@ class ARTIQIRGenerator(algorithm.Visitor): body.append(ir.SetLocal(final_state, ".k", tail)) body.append(ir.Branch(finalizer)) - if not has_catchall: - # Add a catch-all handler so that finally would have a chance - # to execute. - handler = self.add_block("handler.catchall") - landingpad.add_clause(handler, None) - handlers.append((handler, handler)) + cleanup.append(ir.SetLocal(final_state, ".k", reraise)) + cleanup.append(ir.Branch(finalizer)) for handler, post_handler in handlers: if not post_handler.is_terminated(): post_handler.append(ir.SetLocal(final_state, ".k", tail)) - post_handler.append(ir.Branch(tail)) + post_handler.append(ir.Branch(finalizer)) if not post_finalizer.is_terminated(): dest = post_finalizer.append(ir.GetLocal(final_state, ".k")) @@ -594,6 +601,8 @@ class ARTIQIRGenerator(algorithm.Visitor): 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)) diff --git a/artiq/compiler/transforms/dead_code_eliminator.py b/artiq/compiler/transforms/dead_code_eliminator.py index 003b8561c..1159dc0ca 100644 --- a/artiq/compiler/transforms/dead_code_eliminator.py +++ b/artiq/compiler/transforms/dead_code_eliminator.py @@ -15,7 +15,9 @@ class DeadCodeEliminator: def process_function(self, func): for block in func.basic_blocks: - if not any(block.predecessors()) and block != func.entry(): + if not any(block.predecessors()) and \ + not any([isinstance(use, ir.SetLocal) for use in block.uses]) and \ + block != func.entry(): self.remove_block(block) def remove_block(self, block): @@ -25,10 +27,6 @@ class DeadCodeEliminator: use.remove_incoming_block(block) if not any(use.operands): self.remove_instruction(use) - elif isinstance(use, ir.SetLocal): - # Setting the target for `finally` resumption, e.g. - # setlocal(.k) %v.4, label %try.doreturn - use.erase() else: assert False diff --git a/artiq/compiler/transforms/inferencer.py b/artiq/compiler/transforms/inferencer.py index 15604e5b8..57aa9247d 100644 --- a/artiq/compiler/transforms/inferencer.py +++ b/artiq/compiler/transforms/inferencer.py @@ -956,13 +956,14 @@ class Inferencer(algorithm.Visitor): def visit_Raise(self, node): self.generic_visit(node) - exc_type = node.exc.type - if 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.exc.loc) - self.engine.process(diag) + if node.exc is not None: + exc_type = node.exc.type + if 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.exc.loc) + self.engine.process(diag) def visit_Assert(self, node): self.generic_visit(node) diff --git a/artiq/compiler/transforms/llvm_ir_generator.py b/artiq/compiler/transforms/llvm_ir_generator.py index 965ddad8c..8c1eca73b 100644 --- a/artiq/compiler/transforms/llvm_ir_generator.py +++ b/artiq/compiler/transforms/llvm_ir_generator.py @@ -137,13 +137,19 @@ class LLVMIRGenerator: 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) - elif name == "__artiq_raise": - llty = ll.FunctionType(ll.VoidType(), [self.llty_of_type(builtins.TException())]) elif name == "__artiq_personality": llty = ll.FunctionType(ll.IntType(32), [], var_arg=True) + elif name == "__artiq_raise": + llty = ll.FunctionType(ll.VoidType(), [self.llty_of_type(builtins.TException())]) + elif name == "__artiq_reraise": + llty = ll.FunctionType(ll.VoidType(), []) else: assert False - return ll.Function(self.llmodule, llty, name) + + llfun = ll.Function(self.llmodule, llty, name) + if name in ("__artiq_raise", "__artiq_reraise", "llvm.trap"): + llfun.attributes.add("noreturn") + return llfun def map(self, value): if isinstance(value, (ir.Argument, ir.Instruction, ir.BasicBlock)): @@ -599,12 +605,20 @@ class LLVMIRGenerator: llinsn.attributes.add('noreturn') return llinsn + def process_Reraise(self, insn): + llinsn = self.llbuilder.call(self.llbuiltin("__artiq_reraise"), [], + name=insn.name) + llinsn.attributes.add('noreturn') + self.llbuilder.unreachable() + return llinsn + def process_LandingPad(self, insn): # Layout on return from landing pad: {%_Unwind_Exception*, %Exception*} lllandingpadty = ll.LiteralStructType([ll.IntType(8).as_pointer(), ll.IntType(8).as_pointer()]) lllandingpad = self.llbuilder.landingpad(lllandingpadty, - self.llbuiltin("__artiq_personality")) + self.llbuiltin("__artiq_personality"), + 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)]) @@ -627,7 +641,7 @@ class LLVMIRGenerator: self.llbuilder.branch(self.map(target)) if self.llbuilder.basic_block.terminator is None: - self.llbuilder.resume(lllandingpad) + self.llbuilder.branch(self.map(insn.cleanup())) return llexn diff --git a/lit-test/test/exceptions/catch.py b/lit-test/test/exceptions/catch.py index 4d2730859..d6c2866c1 100644 --- a/lit-test/test/exceptions/catch.py +++ b/lit-test/test/exceptions/catch.py @@ -1,4 +1,5 @@ -# RUN: %python -m artiq.compiler.testbench.jit %s +# RUN: %python -m artiq.compiler.testbench.jit %s >%t +# RUN: OutputCheck %s --file-to-check=%t # REQUIRES: exceptions try: diff --git a/lit-test/test/exceptions/catch_all.py b/lit-test/test/exceptions/catch_all.py index 21d8f9581..1417f5f31 100644 --- a/lit-test/test/exceptions/catch_all.py +++ b/lit-test/test/exceptions/catch_all.py @@ -1,4 +1,5 @@ -# RUN: %python -m artiq.compiler.testbench.jit %s +# RUN: %python -m artiq.compiler.testbench.jit %s >%t +# RUN: OutputCheck %s --file-to-check=%t # REQUIRES: exceptions def catch(f): diff --git a/lit-test/test/exceptions/catch_multi.py b/lit-test/test/exceptions/catch_multi.py index fc836e9a0..472086660 100644 --- a/lit-test/test/exceptions/catch_multi.py +++ b/lit-test/test/exceptions/catch_multi.py @@ -1,4 +1,5 @@ -# RUN: %python -m artiq.compiler.testbench.jit %s +# RUN: %python -m artiq.compiler.testbench.jit %s >%t +# RUN: OutputCheck %s --file-to-check=%t # REQUIRES: exceptions def catch(f): diff --git a/lit-test/test/exceptions/catch_outer.py b/lit-test/test/exceptions/catch_outer.py index 0011f5c1b..de7253eaf 100644 --- a/lit-test/test/exceptions/catch_outer.py +++ b/lit-test/test/exceptions/catch_outer.py @@ -1,4 +1,5 @@ -# RUN: %python -m artiq.compiler.testbench.jit %s +# RUN: %python -m artiq.compiler.testbench.jit %s >%t +# RUN: OutputCheck %s --file-to-check=%t # REQUIRES: exceptions def f(): diff --git a/lit-test/test/exceptions/finally.py b/lit-test/test/exceptions/finally.py new file mode 100644 index 000000000..17304fc15 --- /dev/null +++ b/lit-test/test/exceptions/finally.py @@ -0,0 +1,21 @@ +# 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/lit-test/test/exceptions/finally_catch.py b/lit-test/test/exceptions/finally_catch.py new file mode 100644 index 000000000..23bc39730 --- /dev/null +++ b/lit-test/test/exceptions/finally_catch.py @@ -0,0 +1,17 @@ +# 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/lit-test/test/exceptions/finally_raise.py b/lit-test/test/exceptions/finally_raise.py new file mode 100644 index 000000000..02c41ea7e --- /dev/null +++ b/lit-test/test/exceptions/finally_raise.py @@ -0,0 +1,23 @@ +# 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/lit-test/test/exceptions/finally_squash.py b/lit-test/test/exceptions/finally_squash.py new file mode 100644 index 000000000..8c7b58fc3 --- /dev/null +++ b/lit-test/test/exceptions/finally_squash.py @@ -0,0 +1,21 @@ +# 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/lit-test/test/exceptions/reraise.py b/lit-test/test/exceptions/reraise.py new file mode 100644 index 000000000..2a02b523d --- /dev/null +++ b/lit-test/test/exceptions/reraise.py @@ -0,0 +1,16 @@ +# RUN: %not %python -m artiq.compiler.testbench.jit %s >%t +# RUN: OutputCheck %s --file-to-check=%t +# REQUIRES: exceptions + +def f(): + # CHECK-L: Uncaught ZeroDivisionError + # CHECK-L: at input.py:${LINE:+1}: + 1/0 + +def g(): + try: + f() + except: + raise + +g() diff --git a/lit-test/test/exceptions/reraise_update.py b/lit-test/test/exceptions/reraise_update.py new file mode 100644 index 000000000..891e7685d --- /dev/null +++ b/lit-test/test/exceptions/reraise_update.py @@ -0,0 +1,16 @@ +# 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 ZeroDivisionError + # CHECK-L: at input.py:${LINE:+1}: + raise e + +g() diff --git a/lit-test/test/exceptions/uncaught.py b/lit-test/test/exceptions/uncaught.py index 4e056e025..6044f8bf2 100644 --- a/lit-test/test/exceptions/uncaught.py +++ b/lit-test/test/exceptions/uncaught.py @@ -1,6 +1,7 @@ -# RUN: %not %python -m artiq.compiler.testbench.jit %s +# RUN: %not %python -m artiq.compiler.testbench.jit %s >%t +# RUN: OutputCheck %s --file-to-check=%t # REQUIRES: exceptions # CHECK-L: Uncaught ZeroDivisionError: cannot divide by zero (0, 0, 0) -# CHECK-L: at input.py:${LINE:+1}:0 +# CHECK-L: at input.py:${LINE:+1}: 1/0 diff --git a/lit-test/test/integration/finally.py b/lit-test/test/integration/finally.py index 376051e66..fab1fe93d 100644 --- a/lit-test/test/integration/finally.py +++ b/lit-test/test/integration/finally.py @@ -62,3 +62,15 @@ def i(): # CHECK-NOT-L: i-out # CHECK-L: i 30 print("i", i()) + +def j(): + try: + print("j-try") + finally: + print("j-finally") + print("j-out") + +# CHECK-L: j-try +# CHECK-L: j-finally +# CHECK-L: j-out +print("j", j()) diff --git a/soc/runtime/artiq_personality.c b/soc/runtime/artiq_personality.c index b7bbad460..a8c19cfd0 100644 --- a/soc/runtime/artiq_personality.c +++ b/soc/runtime/artiq_personality.c @@ -8,8 +8,8 @@ /* Logging */ #ifndef NDEBUG -#define EH_LOG0(fmt) fprintf(stderr, "__artiq_personality: " fmt "\n") -#define EH_LOG(fmt, ...) fprintf(stderr, "__artiq_personality: " fmt "\n", __VA_ARGS__) +#define EH_LOG0(fmt) fprintf(stderr, "%s: " fmt "\n", __func__) +#define EH_LOG(fmt, ...) fprintf(stderr, "%s: " fmt "\n", __func__, __VA_ARGS__) #else #define EH_LOG0(fmt) #define EH_LOG(fmt, ...) @@ -17,7 +17,7 @@ #define EH_FAIL(err) \ do { \ - fprintf(stderr, "__artiq_personality fatal: %s\n", err); \ + fprintf(stderr, "%s fatal: %s\n", __func__, err); \ abort(); \ } while(0) @@ -195,37 +195,61 @@ static uintptr_t readEncodedPointer(const uint8_t **data, uint8_t encoding) { } -/* Raising and catching */ +/* Raising */ #define ARTIQ_EXCEPTION_CLASS 0x4152545141525451LL // 'ARTQARTQ' +static void __artiq_cleanup(_Unwind_Reason_Code reason, struct _Unwind_Exception *exc); + struct artiq_raised_exception { struct _Unwind_Exception unwind; struct artiq_exception artiq; + int handled; }; +static struct artiq_raised_exception inflight; + +void __artiq_raise(struct artiq_exception *artiq_exn) { + EH_LOG("===> raise (name=%s)", artiq_exn->name); + + memmove(&inflight.artiq, artiq_exn, sizeof(struct artiq_exception)); + inflight.unwind.exception_class = ARTIQ_EXCEPTION_CLASS; + inflight.unwind.exception_cleanup = &__artiq_cleanup; + inflight.handled = 0; + + _Unwind_Reason_Code result = _Unwind_RaiseException(&inflight.unwind); + EH_ASSERT((result == _URC_END_OF_STACK) && + "Unexpected error during unwinding"); + + // If we're here, there are no handlers, only cleanups. + __artiq_terminate(&inflight.artiq); +} + +void __artiq_reraise() { + if(inflight.handled) { + EH_LOG0("===> reraise"); + __artiq_raise(&inflight.artiq); + } else { + EH_LOG0("===> resume"); + EH_ASSERT((inflight.artiq.typeinfo != 0) && + "Need an exception to reraise"); + _Unwind_Resume(&inflight.unwind); + abort(); + } +} + +/* Catching */ + +// The code below does not refer to the `inflight` global. + static void __artiq_cleanup(_Unwind_Reason_Code reason, struct _Unwind_Exception *exc) { + EH_LOG0("===> cleanup"); struct artiq_raised_exception *inflight = (struct artiq_raised_exception*) exc; // The in-flight exception is statically allocated, so we don't need to free it. // But, we clear it to mark it as processed. memset(&inflight->artiq, 0, sizeof(struct artiq_exception)); } -void __artiq_raise(struct artiq_exception *artiq_exn) { - static struct artiq_raised_exception inflight; - memcpy(&inflight.artiq, artiq_exn, sizeof(struct artiq_exception)); - inflight.unwind.exception_class = ARTIQ_EXCEPTION_CLASS; - inflight.unwind.exception_cleanup = &__artiq_cleanup; - - _Unwind_Reason_Code result = _Unwind_RaiseException(&inflight.unwind); - if(result == _URC_END_OF_STACK) { - __artiq_terminate(&inflight.artiq); - } else { - fprintf(stderr, "__artiq_raise: unexpected error (%d)\n", result); - abort(); - } -} - _Unwind_Reason_Code __artiq_personality( int version, _Unwind_Action actions, uint64_t exceptionClass, struct _Unwind_Exception *exceptionObject, struct _Unwind_Context *context) { @@ -345,6 +369,9 @@ _Unwind_Reason_Code __artiq_personality( if(!(actions & _UA_SEARCH_PHASE)) { EH_LOG0("=> jumping to landing pad"); + if(actions & _UA_HANDLER_FRAME) + inflight->handled = 1; + _Unwind_SetGR(context, __builtin_eh_return_data_regno(0), (uintptr_t)exceptionObject); _Unwind_SetGR(context, __builtin_eh_return_data_regno(1), diff --git a/soc/runtime/artiq_personality.h b/soc/runtime/artiq_personality.h index 45b0ef9f1..2e3fc0f45 100644 --- a/soc/runtime/artiq_personality.h +++ b/soc/runtime/artiq_personality.h @@ -19,10 +19,15 @@ struct artiq_exception { extern "C" { #endif -void __artiq_terminate(struct artiq_exception *artiq_exn) +/* Provided by the runtime */ +void __artiq_raise(struct artiq_exception *artiq_exn) + __attribute__((noreturn)); +void __artiq_reraise() __attribute__((noreturn)); -void __artiq_raise(struct artiq_exception *artiq_exn); +/* Called by the runtime */ +void __artiq_terminate(struct artiq_exception *artiq_exn) + __attribute__((noreturn)); #ifdef __cplusplus } diff --git a/t.py b/t.py new file mode 100644 index 000000000..e69de29bb From edffb40ef2dd3789a590ce877b680b5991b5e46d Mon Sep 17 00:00:00 2001 From: whitequark Date: Mon, 27 Jul 2015 13:48:42 +0300 Subject: [PATCH 156/369] On uncaught exception, execute finally clauses and collect backtrace. --- lit-test/libartiq_personality/Makefile | 2 +- .../libartiq_personality/artiq_terminate.c | 17 +++++++- lit-test/test/exceptions/finally_uncaught.py | 12 ++++++ soc/runtime/artiq_personality.c | 42 ++++++++++++++++++- soc/runtime/artiq_personality.h | 9 +++- 5 files changed, 77 insertions(+), 5 deletions(-) create mode 100644 lit-test/test/exceptions/finally_uncaught.py diff --git a/lit-test/libartiq_personality/Makefile b/lit-test/libartiq_personality/Makefile index 4fa32ab96..2a72a7185 100644 --- a/lit-test/libartiq_personality/Makefile +++ b/lit-test/libartiq_personality/Makefile @@ -1,4 +1,4 @@ CC ?= clang libartiq_personality.so: ../../soc/runtime/artiq_personality.c artiq_terminate.c - $(CC) -Wall -Werror -I. -I../../soc/runtime -g -fPIC -shared -o $@ $^ + $(CC) -std=c99 -Wall -Werror -I. -I../../soc/runtime -g -fPIC -shared -o $@ $^ diff --git a/lit-test/libartiq_personality/artiq_terminate.c b/lit-test/libartiq_personality/artiq_terminate.c index c4c2bf7db..5b1315131 100644 --- a/lit-test/libartiq_personality/artiq_terminate.c +++ b/lit-test/libartiq_personality/artiq_terminate.c @@ -4,11 +4,26 @@ #include #include -void __artiq_terminate(struct artiq_exception *exn) { +#define __USE_GNU +#include + +void __artiq_terminate(struct artiq_exception *exn, + struct artiq_backtrace_item *backtrace, + size_t backtrace_size) { printf("Uncaught %s: %s (%"PRIi64", %"PRIi64", %"PRIi64")\n" "at %s:%"PRIi32":%"PRIi32"\n", exn->name, exn->message, exn->param[0], exn->param[1], exn->param[1], exn->file, exn->line, exn->column + 1); + + for(size_t i = 0; i < backtrace_size; i++) { + Dl_info info; + if(dladdr((void*) backtrace[i].function, &info) && info.dli_sname) { + printf("at %s+%p\n", info.dli_sname, (void*)backtrace[i].offset); + } else { + printf("at %p+%p\n", (void*)backtrace[i].function, (void*)backtrace[i].offset); + } + } + exit(1); } diff --git a/lit-test/test/exceptions/finally_uncaught.py b/lit-test/test/exceptions/finally_uncaught.py new file mode 100644 index 000000000..1eb211663 --- /dev/null +++ b/lit-test/test/exceptions/finally_uncaught.py @@ -0,0 +1,12 @@ +# 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/soc/runtime/artiq_personality.c b/soc/runtime/artiq_personality.c index a8c19cfd0..64ee13c31 100644 --- a/soc/runtime/artiq_personality.c +++ b/soc/runtime/artiq_personality.c @@ -200,11 +200,17 @@ static uintptr_t readEncodedPointer(const uint8_t **data, uint8_t encoding) { #define ARTIQ_EXCEPTION_CLASS 0x4152545141525451LL // 'ARTQARTQ' static void __artiq_cleanup(_Unwind_Reason_Code reason, struct _Unwind_Exception *exc); +static _Unwind_Reason_Code __artiq_uncaught_exception( + int version, _Unwind_Action actions, uint64_t exceptionClass, + struct _Unwind_Exception *exceptionObject, struct _Unwind_Context *context, + void *stop_parameter); struct artiq_raised_exception { struct _Unwind_Exception unwind; struct artiq_exception artiq; int handled; + struct artiq_backtrace_item backtrace[1024]; + size_t backtrace_size; }; static struct artiq_raised_exception inflight; @@ -216,13 +222,17 @@ void __artiq_raise(struct artiq_exception *artiq_exn) { inflight.unwind.exception_class = ARTIQ_EXCEPTION_CLASS; inflight.unwind.exception_cleanup = &__artiq_cleanup; inflight.handled = 0; + inflight.backtrace_size = 0; _Unwind_Reason_Code result = _Unwind_RaiseException(&inflight.unwind); EH_ASSERT((result == _URC_END_OF_STACK) && "Unexpected error during unwinding"); // If we're here, there are no handlers, only cleanups. - __artiq_terminate(&inflight.artiq); + // Force unwinding anyway; we shall stop at nothing except the end of stack. + result = _Unwind_ForcedUnwind(&inflight.unwind, &__artiq_uncaught_exception, + NULL); + EH_FAIL("_Unwind_ForcedUnwind should not return"); } void __artiq_reraise() { @@ -238,7 +248,7 @@ void __artiq_reraise() { } } -/* Catching */ +/* Unwinding */ // The code below does not refer to the `inflight` global. @@ -250,6 +260,34 @@ static void __artiq_cleanup(_Unwind_Reason_Code reason, struct _Unwind_Exception memset(&inflight->artiq, 0, sizeof(struct artiq_exception)); } +static _Unwind_Reason_Code __artiq_uncaught_exception( + int version, _Unwind_Action actions, uint64_t exceptionClass, + struct _Unwind_Exception *exceptionObject, struct _Unwind_Context *context, + void *stop_parameter) { + struct artiq_raised_exception *inflight = + (struct artiq_raised_exception*)exceptionObject; + EH_ASSERT(inflight->backtrace_size < + sizeof(inflight->backtrace) / sizeof(inflight->backtrace[0]) && + "Out of space for backtrace"); + + uintptr_t pc = _Unwind_GetIP(context); + uintptr_t funcStart = _Unwind_GetRegionStart(context); + uintptr_t pcOffset = pc - funcStart; + EH_LOG("===> uncaught (pc=%p+%p)", (void*)funcStart, (void*)pcOffset); + + inflight->backtrace[inflight->backtrace_size].function = funcStart; + inflight->backtrace[inflight->backtrace_size].offset = pcOffset; + ++inflight->backtrace_size; + + if(actions & _UA_END_OF_STACK) { + EH_LOG0("end of stack"); + __artiq_terminate(&inflight->artiq, inflight->backtrace, inflight->backtrace_size); + } else { + EH_LOG0("continue"); + return _URC_NO_REASON; + } +} + _Unwind_Reason_Code __artiq_personality( int version, _Unwind_Action actions, uint64_t exceptionClass, struct _Unwind_Exception *exceptionObject, struct _Unwind_Context *context) { diff --git a/soc/runtime/artiq_personality.h b/soc/runtime/artiq_personality.h index 2e3fc0f45..82a5a7124 100644 --- a/soc/runtime/artiq_personality.h +++ b/soc/runtime/artiq_personality.h @@ -15,6 +15,11 @@ struct artiq_exception { int64_t param[3]; }; +struct artiq_backtrace_item { + intptr_t function; + intptr_t offset; +}; + #ifdef __cplusplus extern "C" { #endif @@ -26,7 +31,9 @@ void __artiq_reraise() __attribute__((noreturn)); /* Called by the runtime */ -void __artiq_terminate(struct artiq_exception *artiq_exn) +void __artiq_terminate(struct artiq_exception *artiq_exn, + struct artiq_backtrace_item *backtrace, + size_t backtrace_size) __attribute__((noreturn)); #ifdef __cplusplus From 244ace19e1580230dbea254209511735c592270f Mon Sep 17 00:00:00 2001 From: whitequark Date: Mon, 27 Jul 2015 13:56:18 +0300 Subject: [PATCH 157/369] Add artiq_raise_from_c macro. --- soc/runtime/artiq_personality.h | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/soc/runtime/artiq_personality.h b/soc/runtime/artiq_personality.h index 82a5a7124..fec789d27 100644 --- a/soc/runtime/artiq_personality.h +++ b/soc/runtime/artiq_personality.h @@ -30,6 +30,19 @@ void __artiq_raise(struct artiq_exception *artiq_exn) void __artiq_reraise() __attribute__((noreturn)); +#define artiq_raise_from_c(exnname, exnmsg, exnparam0, exnparam1, exnparam2) \ + do { \ + struct artiq_exception exn = { \ + .name = exnname, \ + .message = exnmsg, \ + .param = { exnparam0, exnparam1, exnparam2 }, \ + .file = __FILE__, \ + .line = __LINE__, \ + .column = 0 \ + }; \ + __artiq_raise(&exn); \ + } while(0) + /* Called by the runtime */ void __artiq_terminate(struct artiq_exception *artiq_exn, struct artiq_backtrace_item *backtrace, From 3378dd57b8fd15172349a99e2b9ae2e8df5b3c1a Mon Sep 17 00:00:00 2001 From: whitequark Date: Wed, 29 Jul 2015 13:54:00 +0300 Subject: [PATCH 158/369] Fold llvmlite patches into m-labs/llvmlite repository. --- artiq/compiler/testbench/jit.py | 3 +- artiq/compiler/testbench/llvmgen.py | 2 +- .../compiler/transforms/llvm_ir_generator.py | 2 +- conda/llvmlite-or1k/bld.bat | 5 -- conda/llvmlite-or1k/build.sh | 3 - doc/manual/installing.rst | 5 +- misc/llvmlite-add-all-targets.patch | 38 ----------- misc/llvmlite-build-as-debug-on-windows.patch | 13 ---- misc/llvmlite-rename.patch | 65 ------------------- setup.py | 3 +- t.py | 0 11 files changed, 6 insertions(+), 133 deletions(-) delete mode 100644 misc/llvmlite-add-all-targets.patch delete mode 100644 misc/llvmlite-build-as-debug-on-windows.patch delete mode 100644 misc/llvmlite-rename.patch delete mode 100644 t.py diff --git a/artiq/compiler/testbench/jit.py b/artiq/compiler/testbench/jit.py index 9e6fad53e..3495684e1 100644 --- a/artiq/compiler/testbench/jit.py +++ b/artiq/compiler/testbench/jit.py @@ -1,12 +1,11 @@ import os, sys, fileinput, ctypes from pythonparser import diagnostic -from llvmlite import binding as llvm +from llvmlite_artiq import binding as llvm from .. import Module llvm.initialize() llvm.initialize_native_target() llvm.initialize_native_asmprinter() -llvm.check_jit_execution() def main(): libartiq_personality = os.getenv('LIBARTIQ_PERSONALITY') diff --git a/artiq/compiler/testbench/llvmgen.py b/artiq/compiler/testbench/llvmgen.py index c32ceb98e..0c3dd5941 100644 --- a/artiq/compiler/testbench/llvmgen.py +++ b/artiq/compiler/testbench/llvmgen.py @@ -1,6 +1,6 @@ import sys, fileinput from pythonparser import diagnostic -from llvmlite import ir as ll +from llvmlite_artiq import ir as ll from .. import Module def main(): diff --git a/artiq/compiler/transforms/llvm_ir_generator.py b/artiq/compiler/transforms/llvm_ir_generator.py index 8c1eca73b..13ad6f47a 100644 --- a/artiq/compiler/transforms/llvm_ir_generator.py +++ b/artiq/compiler/transforms/llvm_ir_generator.py @@ -3,8 +3,8 @@ into LLVM intermediate representation. """ -import llvmlite.ir as ll from pythonparser import ast +from llvmlite_artiq import ir as ll from .. import types, builtins, ir class LLVMIRGenerator: diff --git a/conda/llvmlite-or1k/bld.bat b/conda/llvmlite-or1k/bld.bat index 17e63ad30..bbb38d3c9 100644 --- a/conda/llvmlite-or1k/bld.bat +++ b/conda/llvmlite-or1k/bld.bat @@ -4,10 +4,5 @@ set CMAKE_PREFIX_PATH=%LIBRARY_PREFIX% @rem Ensure there are no build leftovers (CMake can complain) if exist ffi\build rmdir /S /Q ffi\build -@rem Apply patches -patch -p1 < %RECIPE_DIR%/../../misc/llvmlite-add-all-targets.patch -patch -p1 < %RECIPE_DIR%/../../misc/llvmlite-rename.patch -patch -p1 < %RECIPE_DIR%/../../misc/llvmlite-build-as-debug-on-windows.patch - %PYTHON% -S setup.py install if errorlevel 1 exit 1 diff --git a/conda/llvmlite-or1k/build.sh b/conda/llvmlite-or1k/build.sh index 327c15518..d3b3bbe2c 100755 --- a/conda/llvmlite-or1k/build.sh +++ b/conda/llvmlite-or1k/build.sh @@ -1,6 +1,3 @@ #!/bin/bash -patch -p1 < ${RECIPE_DIR}/../../misc/llvmlite-add-all-targets.patch -patch -p1 < ${RECIPE_DIR}/../../misc/llvmlite-rename.patch -patch -p1 < ${RECIPE_DIR}/../../misc/llvmlite-build-as-debug-on-windows.patch PATH=/usr/local/llvm-or1k/bin:$PATH $PYTHON setup.py install diff --git a/doc/manual/installing.rst b/doc/manual/installing.rst index 5ade4cb7e..8de011a29 100644 --- a/doc/manual/installing.rst +++ b/doc/manual/installing.rst @@ -300,11 +300,8 @@ Installing the host-side software $ cd ~/artiq-dev $ git clone https://github.com/m-labs/llvmlite - $ git checkout backport-3.5 + $ git checkout artiq $ cd llvmlite - $ patch -p1 < ~/artiq-dev/artiq/misc/llvmlite-add-all-targets.patch - $ patch -p1 < ~/artiq-dev/artiq/misc/llvmlite-rename.patch - $ patch -p1 < ~/artiq-dev/artiq/misc/llvmlite-build-as-debug-on-windows.patch $ LLVM_CONFIG=/usr/local/llvm-or1k/bin/llvm-config python3 setup.py install --user * Install ARTIQ: :: diff --git a/misc/llvmlite-add-all-targets.patch b/misc/llvmlite-add-all-targets.patch deleted file mode 100644 index 6b52fad9f..000000000 --- a/misc/llvmlite-add-all-targets.patch +++ /dev/null @@ -1,38 +0,0 @@ -diff --git a/ffi/initfini.cpp b/ffi/initfini.cpp -index 42c8965..067be62 100644 ---- a/ffi/initfini.cpp -+++ b/ffi/initfini.cpp -@@ -37,9 +37,10 @@ LLVMPY_Shutdown(){ - // NOTE: it is important that we don't export functions which we don't use, - // especially those which may pull in large amounts of additional code or data. - --// INIT(AllTargetInfos) --// INIT(AllTargets) --// INIT(AllTargetMCs) -+INIT(AllTargetInfos) -+INIT(AllTargets) -+INIT(AllTargetMCs) -+INIT(AllAsmPrinters) - INIT(NativeTarget) - INIT(NativeAsmParser) - INIT(NativeAsmPrinter) -diff --git a/llvmlite/binding/initfini.py b/llvmlite/binding/initfini.py -index bfaa5b2..7d0df11 100644 ---- a/llvmlite/binding/initfini.py -+++ b/llvmlite/binding/initfini.py -@@ -8,6 +8,15 @@ def initialize(): - ffi.lib.LLVMPY_InitializeCore() - - -+def initialize_all_targets(): -+ ffi.lib.LLVMPY_InitializeAllTargetInfos() -+ ffi.lib.LLVMPY_InitializeAllTargets() -+ ffi.lib.LLVMPY_InitializeAllTargetMCs() -+ -+def initialize_all_asmprinters(): -+ ffi.lib.LLVMPY_InitializeAllAsmPrinters() -+ -+ - def initialize_native_target(): - """ - Initialize the native (host) target. Necessary before doing any diff --git a/misc/llvmlite-build-as-debug-on-windows.patch b/misc/llvmlite-build-as-debug-on-windows.patch deleted file mode 100644 index e385fb4a2..000000000 --- a/misc/llvmlite-build-as-debug-on-windows.patch +++ /dev/null @@ -1,13 +0,0 @@ -diff --git a/ffi/build.py b/ffi/build.py -index 3889ba5..58f93ec 100755 ---- a/ffi/build.py -+++ b/ffi/build.py -@@ -58,7 +58,7 @@ def find_win32_generator(): - - def main_win32(): - generator = find_win32_generator() -- config = 'Release' -+ config = 'Debug' - if not os.path.exists(build_dir): - os.mkdir(build_dir) - try_cmake(here_dir, build_dir, generator) diff --git a/misc/llvmlite-rename.patch b/misc/llvmlite-rename.patch deleted file mode 100644 index faea85104..000000000 --- a/misc/llvmlite-rename.patch +++ /dev/null @@ -1,65 +0,0 @@ -diff --git a/setup.py b/setup.py -index 6d28265..f4edd29 100644 ---- a/setup.py -+++ b/setup.py -@@ -15,10 +15,10 @@ from llvmlite.utils import get_library_files - import versioneer - - versioneer.VCS = 'git' --versioneer.versionfile_source = 'llvmlite/_version.py' --versioneer.versionfile_build = 'llvmlite/_version.py' -+versioneer.versionfile_source = 'llvmlite_or1k/_version.py' -+versioneer.versionfile_build = 'llvmlite_or1k/_version.py' - versioneer.tag_prefix = 'v' # tags are like v1.2.0 --versioneer.parentdir_prefix = 'llvmlite-' # dirname like 'myproject-1.2.0' -+versioneer.parentdir_prefix = 'llvmlite_or1k-' # dirname like 'myproject-1.2.0' - - - here_dir = os.path.dirname(__file__) -@@ -54,7 +54,7 @@ class LlvmliteBuildExt(build_ext): - # HACK: this makes sure the library file (which is large) is only - # included in binary builds, not source builds. - self.distribution.package_data = { -- "llvmlite.binding": get_library_files(), -+ "llvmlite_or1k.binding": get_library_files(), - } - - -@@ -63,7 +63,7 @@ class LlvmliteInstall(install): - # This seems to only be necessary on OSX. - def run(self): - self.distribution.package_data = { -- "llvmlite.binding": get_library_files(), -+ "llvmlite_or1k.binding": get_library_files(), - } - install.run(self) - -@@ -74,14 +74,14 @@ cmdclass.update({'build': LlvmliteBuild, - }) - - --packages = ['llvmlite', -- 'llvmlite.binding', -- 'llvmlite.ir', -- 'llvmlite.llvmpy', -- 'llvmlite.tests', -+packages = ['llvmlite_or1k', -+ 'llvmlite_or1k.binding', -+ 'llvmlite_or1k.ir', -+ 'llvmlite_or1k.llvmpy', -+ 'llvmlite_or1k.tests', - ] - --setup(name='llvmlite', -+setup(name='llvmlite_or1k', - description="lightweight wrapper around basic LLVM functionality", - version=versioneer.get_version(), - classifiers=[ -@@ -96,6 +96,7 @@ setup(name='llvmlite', - "Topic :: Software Development :: Code Generators", - "Topic :: Software Development :: Compilers", - ], -+ package_dir={"llvmlite_or1k" : "llvmlite"}, - # Include the separately-compiled shared library - author="Continuum Analytics, Inc.", - author_email="numba-users@continuum.io", diff --git a/setup.py b/setup.py index fd645fcf0..2d6870f70 100755 --- a/setup.py +++ b/setup.py @@ -20,7 +20,8 @@ class PushDocCommand(Command): requirements = [ "sphinx", "sphinx-argparse", "pyserial", "numpy", "scipy", "python-dateutil", "prettytable", "h5py", "pydaqmx", "pyelftools", - "quamash", "pyqtgraph", "pythonparser", "lit", "OutputCheck" + "quamash", "pyqtgraph", "pythonparser", + "llvmlite_artiq", "lit", "OutputCheck" ] scripts = [ diff --git a/t.py b/t.py deleted file mode 100644 index e69de29bb..000000000 From 2cd25f85bfedceeed75747b3077245f12524456d Mon Sep 17 00:00:00 2001 From: whitequark Date: Wed, 29 Jul 2015 14:32:34 +0300 Subject: [PATCH 159/369] =?UTF-8?q?Rename=20artiq.compiler.testbench.{modu?= =?UTF-8?q?le=20=E2=86=92=20signature}.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- artiq/compiler/testbench/{module.py => signature.py} | 0 lit-test/test/codegen/warning_useless_bool.py | 2 +- lit-test/test/local_access/invalid.py | 2 +- lit-test/test/local_access/valid.py | 2 +- lit-test/test/monomorphism/error_notmono.py | 2 +- lit-test/test/monomorphism/integers.py | 2 +- 6 files changed, 5 insertions(+), 5 deletions(-) rename artiq/compiler/testbench/{module.py => signature.py} (100%) diff --git a/artiq/compiler/testbench/module.py b/artiq/compiler/testbench/signature.py similarity index 100% rename from artiq/compiler/testbench/module.py rename to artiq/compiler/testbench/signature.py diff --git a/lit-test/test/codegen/warning_useless_bool.py b/lit-test/test/codegen/warning_useless_bool.py index f76e011c8..d81fa2941 100644 --- a/lit-test/test/codegen/warning_useless_bool.py +++ b/lit-test/test/codegen/warning_useless_bool.py @@ -1,4 +1,4 @@ -# RUN: %python -m artiq.compiler.testbench.module +diag %s >%t +# 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 diff --git a/lit-test/test/local_access/invalid.py b/lit-test/test/local_access/invalid.py index c67272eef..fabd6e9a8 100644 --- a/lit-test/test/local_access/invalid.py +++ b/lit-test/test/local_access/invalid.py @@ -1,4 +1,4 @@ -# RUN: %python -m artiq.compiler.testbench.module +diag %s >%t +# RUN: %python -m artiq.compiler.testbench.signature +diag %s >%t # RUN: OutputCheck %s --file-to-check=%t x = 1 diff --git a/lit-test/test/local_access/valid.py b/lit-test/test/local_access/valid.py index 0e598ed39..3c5fd0208 100644 --- a/lit-test/test/local_access/valid.py +++ b/lit-test/test/local_access/valid.py @@ -1,4 +1,4 @@ -# RUN: %python -m artiq.compiler.testbench.module %s >%t +# RUN: %python -m artiq.compiler.testbench.signature %s >%t if False: x = 1 diff --git a/lit-test/test/monomorphism/error_notmono.py b/lit-test/test/monomorphism/error_notmono.py index cf94db697..9c9b02452 100644 --- a/lit-test/test/monomorphism/error_notmono.py +++ b/lit-test/test/monomorphism/error_notmono.py @@ -1,4 +1,4 @@ -# RUN: %python -m artiq.compiler.testbench.module +diag %s >%t +# RUN: %python -m artiq.compiler.testbench.signature +diag %s >%t # RUN: OutputCheck %s --file-to-check=%t # CHECK-L: ${LINE:+1}: error: the type of this expression cannot be fully inferred diff --git a/lit-test/test/monomorphism/integers.py b/lit-test/test/monomorphism/integers.py index 9e6ba2884..20850cb47 100644 --- a/lit-test/test/monomorphism/integers.py +++ b/lit-test/test/monomorphism/integers.py @@ -1,4 +1,4 @@ -# RUN: %python -m artiq.compiler.testbench.module %s >%t +# RUN: %python -m artiq.compiler.testbench.signature %s >%t # RUN: OutputCheck %s --file-to-check=%t x = 1 From e8c107925c2a81052563c9ebf82562f9dfc3f4b5 Mon Sep 17 00:00:00 2001 From: whitequark Date: Wed, 29 Jul 2015 20:35:16 +0300 Subject: [PATCH 160/369] Implement shared object linking. --- artiq/compiler/ir.py | 5 ++ artiq/compiler/module.py | 11 ++- artiq/compiler/targets/__init__.py | 87 +++++++++++++++++++ artiq/compiler/testbench/jit.py | 14 ++- artiq/compiler/testbench/llvmgen.py | 6 +- artiq/compiler/testbench/shlib.py | 30 +++++++ .../compiler/transforms/artiq_ir_generator.py | 7 +- .../compiler/transforms/llvm_ir_generator.py | 18 ++-- 8 files changed, 155 insertions(+), 23 deletions(-) create mode 100644 artiq/compiler/targets/__init__.py create mode 100644 artiq/compiler/testbench/shlib.py diff --git a/artiq/compiler/ir.py b/artiq/compiler/ir.py index 7cb73ed30..5cb2382f2 100644 --- a/artiq/compiler/ir.py +++ b/artiq/compiler/ir.py @@ -387,12 +387,17 @@ class Argument(NamedValue): class Function: """ A function containing SSA IR. + + :ivar is_internal: + (bool) if True, the function should not be accessible from outside + the module it is contained in """ def __init__(self, typ, name, arguments): self.type, self.name = typ, name self.names, self.arguments, self.basic_blocks = set(), [], [] self.set_arguments(arguments) + self.is_internal = False def _remove_name(self, name): self.names.remove(name) diff --git a/artiq/compiler/module.py b/artiq/compiler/module.py index 9903eaaeb..133284e95 100644 --- a/artiq/compiler/module.py +++ b/artiq/compiler/module.py @@ -21,7 +21,6 @@ class Module: artiq_ir_generator = transforms.ARTIQIRGenerator(engine=engine, module_name=self.name) dead_code_eliminator = transforms.DeadCodeEliminator(engine=engine) local_access_validator = validators.LocalAccessValidator(engine=engine) - llvm_ir_generator = transforms.LLVMIRGenerator(engine=engine, module_name=self.name) self.parsetree, self.comments = parse_buffer(source_buffer, engine=engine) self.typedtree = asttyped_rewriter.visit(self.parsetree) @@ -34,7 +33,15 @@ class Module: self.artiq_ir = artiq_ir_generator.visit(self.typedtree) dead_code_eliminator.process(self.artiq_ir) # local_access_validator.process(self.artiq_ir) - self.llvm_ir = llvm_ir_generator.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(module_name=self.name, target=target) + return llvm_ir_generator.process(self.artiq_ir) + + def entry_point(self): + """Return the name of the function that is the entry point of this module.""" + return self.name + ".__modinit__" @classmethod def from_string(cls, source_string, name="input.py", first_line=1, engine=None): diff --git a/artiq/compiler/targets/__init__.py b/artiq/compiler/targets/__init__.py new file mode 100644 index 000000000..141a7433f --- /dev/null +++ b/artiq/compiler/targets/__init__.py @@ -0,0 +1,87 @@ +import tempfile, subprocess +from llvmlite_artiq import ir as ll, binding as llvm + +llvm.initialize() +llvm.initialize_all_targets() +llvm.initialize_all_asmprinters() + +class Target: + """ + A description of the target environment where the binaries + generaed by the ARTIQ compiler will be deployed. + + :var triple: (string) + LLVM target triple, e.g. ``"or1k"`` + :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"``. + """ + triple = "unknown" + features = [] + print_function = "printf" + + def __init__(self): + self.llcontext = ll.Context() + + def link(self, objects, init_fn): + """Link the relocatable objects into a shared library for this target.""" + files = [] + + def make_tempfile(data=b""): + f = tempfile.NamedTemporaryFile() + files.append(f) + f.write(data) + f.flush() + return f + + output_file = make_tempfile() + cmdline = [self.triple + "-ld", "-shared", "--eh-frame-hdr", "-init", init_fn] + \ + [make_tempfile(obj).name for obj in objects] + \ + ["-o", output_file.name] + linker = subprocess.Popen(cmdline, stderr=subprocess.PIPE) + stdout, stderr = linker.communicate() + if linker.returncode != 0: + raise Exception("Linker invocation failed: " + stderr.decode('utf-8')) + + output = output_file.read() + + for f in files: + f.close() + + return output + + def compile(self, module): + """Compile the module to a relocatable object for this target.""" + llmod = module.build_llvm_ir(self) + llparsedmod = llvm.parse_assembly(str(llmod)) + llparsedmod.verify() + + llpassmgrbuilder = llvm.create_pass_manager_builder() + llpassmgrbuilder.opt_level = 2 # -O2 + llpassmgrbuilder.size_level = 1 # -Os + + llpassmgr = llvm.create_module_pass_manager() + llpassmgrbuilder.populate(llpassmgr) + llpassmgr.run(llparsedmod) + + lltarget = llvm.Target.from_triple(self.triple) + llmachine = lltarget.create_target_machine( + features=",".join(self.features), + reloc="pic", codemodel="default") + return llmachine.emit_object(llparsedmod) + + def compile_and_link(self, modules): + return self.link([self.compile(module) for module in modules], + init_fn=modules[0].entry_point()) + +class NativeTarget(Target): + def __init__(self): + super().__init__() + self.triple = llvm.get_default_triple() + +class OR1KTarget(Target): + triple = "or1k-linux" + attributes = ["mul", "div", "ffl1", "cmov", "addc"] + print_function = "log" diff --git a/artiq/compiler/testbench/jit.py b/artiq/compiler/testbench/jit.py index 3495684e1..4e99504c1 100644 --- a/artiq/compiler/testbench/jit.py +++ b/artiq/compiler/testbench/jit.py @@ -2,10 +2,7 @@ import os, sys, fileinput, ctypes from pythonparser import diagnostic from llvmlite_artiq import binding as llvm from .. import Module - -llvm.initialize() -llvm.initialize_native_target() -llvm.initialize_native_asmprinter() +from ..targets import NativeTarget def main(): libartiq_personality = os.getenv('LIBARTIQ_PERSONALITY') @@ -22,14 +19,15 @@ def main(): source = "".join(fileinput.input()) source = source.replace("#ARTIQ#", "") - llmod = Module.from_string(source.expandtabs(), engine=engine).llvm_ir + mod = Module.from_string(source.expandtabs(), engine=engine) - lltarget = llvm.Target.from_default_triple() - llmachine = lltarget.create_target_machine() + 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) - lljit.finalize_object() llmain = lljit.get_pointer_to_global(llparsedmod.get_function(llmod.name + ".__modinit__")) ctypes.CFUNCTYPE(None)(llmain)() diff --git a/artiq/compiler/testbench/llvmgen.py b/artiq/compiler/testbench/llvmgen.py index 0c3dd5941..3f72569ac 100644 --- a/artiq/compiler/testbench/llvmgen.py +++ b/artiq/compiler/testbench/llvmgen.py @@ -2,6 +2,7 @@ import sys, fileinput from pythonparser import diagnostic from llvmlite_artiq import ir as ll from .. import Module +from ..targets import NativeTarget def main(): def process_diagnostic(diag): @@ -12,7 +13,10 @@ def main(): engine = diagnostic.Engine() engine.process = process_diagnostic - llmod = Module.from_string("".join(fileinput.input()).expandtabs(), engine=engine).llvm_ir + mod = Module.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") diff --git a/artiq/compiler/testbench/shlib.py b/artiq/compiler/testbench/shlib.py new file mode 100644 index 000000000..c3236129a --- /dev/null +++ b/artiq/compiler/testbench/shlib.py @@ -0,0 +1,30 @@ +import sys, os +from pythonparser import diagnostic +from .. import Module +from ..targets import OR1KTarget + +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.from_filename(filename, engine=engine)) + + llobj = OR1KTarget().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/transforms/artiq_ir_generator.py b/artiq/compiler/transforms/artiq_ir_generator.py index 8c649a25b..3872e014e 100644 --- a/artiq/compiler/transforms/artiq_ir_generator.py +++ b/artiq/compiler/transforms/artiq_ir_generator.py @@ -155,7 +155,7 @@ class ARTIQIRGenerator(algorithm.Visitor): # Statement visitors - def visit_function(self, node, is_lambda): + def visit_function(self, node, is_lambda, is_internal): if is_lambda: name = "lambda.{}.{}".format(node.loc.line(), node.loc.column()) typ = node.type.find() @@ -185,6 +185,7 @@ class ARTIQIRGenerator(algorithm.Visitor): optargs.append(ir.Argument(ir.TOption(typ.optargs[arg_name]), "arg." + arg_name)) func = ir.Function(typ, ".".join(self.name), [env_arg] + args + optargs) + func.is_internal = is_internal self.functions.append(func) old_func, self.current_function = self.current_function, func @@ -237,7 +238,7 @@ class ARTIQIRGenerator(algorithm.Visitor): return self.append(ir.Closure(func, self.current_env)) def visit_FunctionDefT(self, node): - func = self.visit_function(node, is_lambda=False) + func = self.visit_function(node, is_lambda=False, is_internal=len(name) > 2) self._set_local(node.name, func) def visit_Return(self, node): @@ -614,7 +615,7 @@ class ARTIQIRGenerator(algorithm.Visitor): # the IR. def visit_LambdaT(self, node): - return self.visit_function(node, is_lambda=True) + return self.visit_function(node, is_lambda=True, is_internal=True) def visit_IfExpT(self, node): cond = self.visit(node.test) diff --git a/artiq/compiler/transforms/llvm_ir_generator.py b/artiq/compiler/transforms/llvm_ir_generator.py index 13ad6f47a..5d5e4addb 100644 --- a/artiq/compiler/transforms/llvm_ir_generator.py +++ b/artiq/compiler/transforms/llvm_ir_generator.py @@ -8,10 +8,11 @@ from llvmlite_artiq import ir as ll from .. import types, builtins, ir class LLVMIRGenerator: - def __init__(self, engine, module_name, context=ll.Context()): - self.engine = engine - self.llcontext = context + def __init__(self, module_name, target): + self.target = target + self.llcontext = target.llcontext self.llmodule = ll.Module(context=self.llcontext, name=module_name) + self.llmodule.triple = target.triple self.llfunction = None self.llmap = {} self.fixups = [] @@ -135,7 +136,7 @@ class LLVMIRGenerator: 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": + elif name == self.target.print_function: llty = ll.FunctionType(ll.VoidType(), [ll.IntType(8).as_pointer()], var_arg=True) elif name == "__artiq_personality": llty = ll.FunctionType(ll.IntType(32), [], var_arg=True) @@ -161,10 +162,7 @@ class LLVMIRGenerator: if llfun is None: llfun = ll.Function(self.llmodule, self.llty_of_type(value.type, bare=True), value.name) - llfun.linkage = 'internal' - return llfun - else: - return llfun + return llfun else: assert False @@ -184,6 +182,8 @@ class LLVMIRGenerator: llfunty = ll.FunctionType(args=llargtys, return_type=self.llty_of_type(func.type.ret, for_return=True)) self.llfunction = ll.Function(self.llmodule, llfunty, func.name) + self.llfunction.attributes.add('uwtable') + if func.is_internal: self.llfunction.linkage = 'internal' self.llmap = {} @@ -527,7 +527,7 @@ class LLVMIRGenerator: elif insn.op == "printf": # We only get integers, floats, pointers and strings here. llargs = map(self.map, insn.operands) - return self.llbuilder.call(self.llbuiltin("printf"), llargs, + return self.llbuilder.call(self.llbuiltin(self.target.print_function), llargs, name=insn.name) elif insn.op == "exncast": # This is an identity cast at LLVM IR level. From 3b5d3e2b1a3e11a45c4f83b28d59c4eef300a60b Mon Sep 17 00:00:00 2001 From: whitequark Date: Wed, 29 Jul 2015 21:17:52 +0300 Subject: [PATCH 161/369] Add a performance measurement testbench. --- artiq/compiler/testbench/perf.py | 33 ++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 artiq/compiler/testbench/perf.py diff --git a/artiq/compiler/testbench/perf.py b/artiq/compiler/testbench/perf.py new file mode 100644 index 000000000..036409b85 --- /dev/null +++ b/artiq/compiler/testbench/perf.py @@ -0,0 +1,33 @@ +import sys, os, time +from pythonparser import diagnostic +from .. import Module +from ..targets import OR1KTarget + +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.from_filename(filename, engine=engine)) + + runs = 100 + start = time.perf_counter() + for _ in range(runs): + llobj = OR1KTarget().compile_and_link(modules) + end = time.perf_counter() + + print("{} compilation runs: {:.2f}s, {:.2f}ms/run".format( + runs, end - start, (end - start) / runs * 1000)) + +if __name__ == "__main__": + main() From 6d8d0ff3f57cab688db1cbe7805cd86d747c89b3 Mon Sep 17 00:00:00 2001 From: whitequark Date: Wed, 29 Jul 2015 21:28:07 +0300 Subject: [PATCH 162/369] Update performance testbench to include time spent in ARTIQ. --- artiq/compiler/testbench/perf.py | 32 +++++++++++++++++++++++--------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/artiq/compiler/testbench/perf.py b/artiq/compiler/testbench/perf.py index 036409b85..7b0b81487 100644 --- a/artiq/compiler/testbench/perf.py +++ b/artiq/compiler/testbench/perf.py @@ -16,18 +16,32 @@ def main(): engine = diagnostic.Engine() engine.process = process_diagnostic - modules = [] + # Make sure everything's valid + modules = [Module.from_filename(filename, engine=engine) + for filename in sys.argv[1:]] + + def benchmark(f, name): + start = time.perf_counter() + end = 0 + runs = 0 + while end - start < 5 or runs < 10: + f() + runs += 1 + end = time.perf_counter() + + print("{} {} runs: {:.2f}s, {:.2f}ms/run".format( + runs, name, end - start, (end - start) / runs * 1000)) + + sources = [] for filename in sys.argv[1:]: - modules.append(Module.from_filename(filename, engine=engine)) + with open(filename) as f: + sources.append(f.read()) - runs = 100 - start = time.perf_counter() - for _ in range(runs): - llobj = OR1KTarget().compile_and_link(modules) - end = time.perf_counter() + benchmark(lambda: [Module.from_string(src) for src in sources], + "ARTIQ typechecking and transforms") - print("{} compilation runs: {:.2f}s, {:.2f}ms/run".format( - runs, end - start, (end - start) / runs * 1000)) + benchmark(lambda: OR1KTarget().compile_and_link(modules), + "LLVM optimization and linking") if __name__ == "__main__": main() From d7f9af4bb5acde1b2a56ac1b218f709258bd1770 Mon Sep 17 00:00:00 2001 From: whitequark Date: Wed, 29 Jul 2015 21:36:31 +0300 Subject: [PATCH 163/369] Fix accidentally quadratic code in compiler.ir.Function._add_name. --- artiq/compiler/ir.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/artiq/compiler/ir.py b/artiq/compiler/ir.py index 5cb2382f2..327e8c5f6 100644 --- a/artiq/compiler/ir.py +++ b/artiq/compiler/ir.py @@ -396,6 +396,7 @@ class Function: def __init__(self, typ, name, arguments): self.type, self.name = typ, name self.names, self.arguments, self.basic_blocks = set(), [], [] + self.next_name = 1 self.set_arguments(arguments) self.is_internal = False @@ -403,13 +404,14 @@ class Function: self.names.remove(name) def _add_name(self, base_name): - name, counter = base_name, 1 - while name in self.names or name == "": - if base_name == "": - name = "v.{}".format(str(counter)) - else: - name = "{}.{}".format(name, counter) - counter += 1 + if base_name == "": + name = "v.{}".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 From b0185f3917c9fd7911a2c1fd944f349c32b0df22 Mon Sep 17 00:00:00 2001 From: whitequark Date: Wed, 29 Jul 2015 22:23:22 +0300 Subject: [PATCH 164/369] Add profiling to the performance testbench. --- artiq/compiler/testbench/perf.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/artiq/compiler/testbench/perf.py b/artiq/compiler/testbench/perf.py index 7b0b81487..c24946fb6 100644 --- a/artiq/compiler/testbench/perf.py +++ b/artiq/compiler/testbench/perf.py @@ -1,4 +1,4 @@ -import sys, os, time +import sys, os, time, cProfile as profile, pstats from pythonparser import diagnostic from .. import Module from ..targets import OR1KTarget @@ -21,6 +21,9 @@ def main(): for filename in sys.argv[1:]] def benchmark(f, name): + profiler = profile.Profile() + profiler.enable() + start = time.perf_counter() end = 0 runs = 0 @@ -29,9 +32,14 @@ def main(): 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) + sources = [] for filename in sys.argv[1:]: with open(filename) as f: From 1e3911ed39840b6ed899bbd408bb67b540a49db6 Mon Sep 17 00:00:00 2001 From: whitequark Date: Thu, 30 Jul 2015 10:33:54 +0300 Subject: [PATCH 165/369] Use try..finally in compiler.targets.Target.link. --- artiq/compiler/targets/__init__.py | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/artiq/compiler/targets/__init__.py b/artiq/compiler/targets/__init__.py index 141a7433f..e0715674b 100644 --- a/artiq/compiler/targets/__init__.py +++ b/artiq/compiler/targets/__init__.py @@ -8,7 +8,7 @@ llvm.initialize_all_asmprinters() class Target: """ A description of the target environment where the binaries - generaed by the ARTIQ compiler will be deployed. + generated by the ARTIQ compiler will be deployed. :var triple: (string) LLVM target triple, e.g. ``"or1k"`` @@ -36,21 +36,20 @@ class Target: f.flush() return f - output_file = make_tempfile() - cmdline = [self.triple + "-ld", "-shared", "--eh-frame-hdr", "-init", init_fn] + \ - [make_tempfile(obj).name for obj in objects] + \ - ["-o", output_file.name] - linker = subprocess.Popen(cmdline, stderr=subprocess.PIPE) - stdout, stderr = linker.communicate() - if linker.returncode != 0: - raise Exception("Linker invocation failed: " + stderr.decode('utf-8')) + try: + output_file = make_tempfile() + cmdline = [self.triple + "-ld", "-shared", "--eh-frame-hdr", "-init", init_fn] + \ + [make_tempfile(obj).name for obj in objects] + \ + ["-o", output_file.name] + linker = subprocess.Popen(cmdline, stderr=subprocess.PIPE) + stdout, stderr = linker.communicate() + if linker.returncode != 0: + raise Exception("Linker invocation failed: " + stderr.decode('utf-8')) - output = output_file.read() - - for f in files: - f.close() - - return output + return output_file.read() + finally: + for f in files: + f.close() def compile(self, module): """Compile the module to a relocatable object for this target.""" From e8943a008cdf4363e29d51ef56686f7045003623 Mon Sep 17 00:00:00 2001 From: whitequark Date: Thu, 30 Jul 2015 10:35:04 +0300 Subject: [PATCH 166/369] =?UTF-8?q?Rename=20compiler/{targets/=5F=5Finit?= =?UTF-8?q?=5F=5F.py=20=E2=86=92=20targets.py}.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- artiq/compiler/{targets/__init__.py => targets.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename artiq/compiler/{targets/__init__.py => targets.py} (100%) diff --git a/artiq/compiler/targets/__init__.py b/artiq/compiler/targets.py similarity index 100% rename from artiq/compiler/targets/__init__.py rename to artiq/compiler/targets.py From 697b78ddf2fbdf02b300f477c447f0547b8dce13 Mon Sep 17 00:00:00 2001 From: whitequark Date: Thu, 30 Jul 2015 13:45:57 +0300 Subject: [PATCH 167/369] =?UTF-8?q?Rename=20{kserver=20=E2=86=92=20net=5Fs?= =?UTF-8?q?erver}.{c,h}.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- soc/runtime/Makefile | 2 +- soc/runtime/kserver.h | 7 --- soc/runtime/main.c | 6 +-- soc/runtime/{kserver.c => net_server.c} | 66 ++++++++++++------------- soc/runtime/net_server.h | 7 +++ 5 files changed, 44 insertions(+), 44 deletions(-) delete mode 100644 soc/runtime/kserver.h rename soc/runtime/{kserver.c => net_server.c} (68%) create mode 100644 soc/runtime/net_server.h diff --git a/soc/runtime/Makefile b/soc/runtime/Makefile index 673235928..36fb42b2b 100644 --- a/soc/runtime/Makefile +++ b/soc/runtime/Makefile @@ -1,6 +1,6 @@ include $(MSCDIR)/software/common.mak -OBJECTS := isr.o flash_storage.o clock.o rtiocrg.o elf_loader.o services.o session.o log.o test_mode.o kloader.o bridge_ctl.o mailbox.o ksupport_data.o kserver.o moninj.o main.o +OBJECTS := isr.o flash_storage.o clock.o rtiocrg.o elf_loader.o services.o session.o log.o test_mode.o kloader.o bridge_ctl.o mailbox.o ksupport_data.o net_server.o moninj.o main.o OBJECTS_KSUPPORT := ksupport.o exception_jmp.o exceptions.o mailbox.o bridge.o rtio.o ttl.o dds.o CFLAGS += -Ilwip/src/include -Iliblwip diff --git a/soc/runtime/kserver.h b/soc/runtime/kserver.h deleted file mode 100644 index 28eb79ecc..000000000 --- a/soc/runtime/kserver.h +++ /dev/null @@ -1,7 +0,0 @@ -#ifndef __KSERVER_H -#define __KSERVER_H - -void kserver_init(void); -void kserver_service(void); - -#endif /* __KSERVER_H */ diff --git a/soc/runtime/main.c b/soc/runtime/main.c index 74df38aea..bdd9d4b8c 100644 --- a/soc/runtime/main.c +++ b/soc/runtime/main.c @@ -26,7 +26,7 @@ #include "clock.h" #include "rtiocrg.h" #include "test_mode.h" -#include "kserver.h" +#include "net_server.h" #include "session.h" #include "moninj.h" @@ -138,14 +138,14 @@ static void regular_main(void) { puts("Accepting sessions on Ethernet."); network_init(); - kserver_init(); + net_server_init(); moninj_init(); session_end(); while(1) { lwip_service(); kloader_service_essential_kmsg(); - kserver_service(); + net_server_service(); } } diff --git a/soc/runtime/kserver.c b/soc/runtime/net_server.c similarity index 68% rename from soc/runtime/kserver.c rename to soc/runtime/net_server.c index 4d75da8bc..a28b396bd 100644 --- a/soc/runtime/kserver.c +++ b/soc/runtime/net_server.c @@ -14,19 +14,19 @@ #include #include "session.h" -#include "kserver.h" +#include "net_server.h" -struct kserver_connstate { +struct net_server_connstate { int magic_recognized; struct pbuf *rp; int rp_offset; }; -static struct kserver_connstate *cs_new(void) +static struct net_server_connstate *cs_new(void) { - struct kserver_connstate *cs; + struct net_server_connstate *cs; - cs = (struct kserver_connstate *)mem_malloc(sizeof(struct kserver_connstate)); + cs = (struct net_server_connstate *)mem_malloc(sizeof(struct net_server_connstate)); if(!cs) return NULL; cs->magic_recognized = 0; @@ -35,24 +35,24 @@ static struct kserver_connstate *cs_new(void) return cs; } -static void cs_free(struct kserver_connstate *cs) +static void cs_free(struct net_server_connstate *cs) { if(cs->rp) pbuf_free(cs->rp); mem_free(cs); } -static const char kserver_magic[] = "ARTIQ coredev\n"; +static const char net_server_magic[] = "ARTIQ coredev\n"; -static int magic_ok(struct kserver_connstate *cs) +static int magic_ok(struct net_server_connstate *cs) { return cs->magic_recognized >= 14; } -static struct kserver_connstate *active_cs; +static struct net_server_connstate *active_cs; static struct tcp_pcb *active_pcb; -static void kserver_close(struct kserver_connstate *cs, struct tcp_pcb *pcb) +static void net_server_close(struct net_server_connstate *cs, struct tcp_pcb *pcb) { if(cs == active_cs) { session_end(); @@ -70,11 +70,11 @@ static void kserver_close(struct kserver_connstate *cs, struct tcp_pcb *pcb) tcp_close(pcb); } -static err_t kserver_recv(void *arg, struct tcp_pcb *pcb, struct pbuf *p, err_t err) +static err_t net_server_recv(void *arg, struct tcp_pcb *pcb, struct pbuf *p, err_t err) { - struct kserver_connstate *cs; + struct net_server_connstate *cs; - cs = (struct kserver_connstate *)arg; + cs = (struct net_server_connstate *)arg; if(p) { if(cs->rp) pbuf_cat(cs->rp, p); @@ -83,11 +83,11 @@ static err_t kserver_recv(void *arg, struct tcp_pcb *pcb, struct pbuf *p, err_t cs->rp_offset = 0; } } else - kserver_close(cs, pcb); + net_server_close(cs, pcb); return ERR_OK; } -static err_t kserver_sent(void *arg, struct tcp_pcb *pcb, u16_t len) +static err_t net_server_sent(void *arg, struct tcp_pcb *pcb, u16_t len) { session_ack_mem(len); return ERR_OK; @@ -95,13 +95,13 @@ static err_t kserver_sent(void *arg, struct tcp_pcb *pcb, u16_t len) static void tcp_pcb_service(void *arg, struct tcp_pcb *pcb) { - struct kserver_connstate *cs; + struct net_server_connstate *cs; int remaining_in_pbuf; char *rpp; struct pbuf *next; int r; - cs = (struct kserver_connstate *)arg; + cs = (struct net_server_connstate *)arg; while(cs->rp) { remaining_in_pbuf = cs->rp->len - cs->rp_offset; @@ -116,20 +116,20 @@ static void tcp_pcb_service(void *arg, struct tcp_pcb *pcb) } else if(r == 0) return; else - kserver_close(cs, pcb); + net_server_close(cs, pcb); } else { - if(rpp[cs->rp_offset] == kserver_magic[cs->magic_recognized]) { + if(rpp[cs->rp_offset] == net_server_magic[cs->magic_recognized]) { cs->magic_recognized++; if(magic_ok(cs)) { if(active_cs) - kserver_close(active_cs, active_pcb); + net_server_close(active_cs, active_pcb); session_start(); active_cs = cs; active_pcb = pcb; - tcp_sent(pcb, kserver_sent); + tcp_sent(pcb, net_server_sent); } } else { - kserver_close(cs, pcb); + net_server_close(cs, pcb); return; } remaining_in_pbuf--; @@ -150,41 +150,41 @@ static void tcp_pcb_service(void *arg, struct tcp_pcb *pcb) } } -static void kserver_err(void *arg, err_t err) +static void net_server_err(void *arg, err_t err) { - struct kserver_connstate *cs; + struct net_server_connstate *cs; - cs = (struct kserver_connstate *)arg; + cs = (struct net_server_connstate *)arg; cs_free(cs); } static struct tcp_pcb *listen_pcb; -static err_t kserver_accept(void *arg, struct tcp_pcb *newpcb, err_t err) +static err_t net_server_accept(void *arg, struct tcp_pcb *newpcb, err_t err) { - struct kserver_connstate *cs; + struct net_server_connstate *cs; cs = cs_new(); if(!cs) return ERR_MEM; tcp_accepted(listen_pcb); tcp_arg(newpcb, cs); - tcp_recv(newpcb, kserver_recv); - tcp_err(newpcb, kserver_err); + tcp_recv(newpcb, net_server_recv); + tcp_err(newpcb, net_server_err); return ERR_OK; } -void kserver_init(void) +void net_server_init(void) { listen_pcb = tcp_new(); tcp_bind(listen_pcb, IP_ADDR_ANY, 1381); listen_pcb = tcp_listen(listen_pcb); - tcp_accept(listen_pcb, kserver_accept); + tcp_accept(listen_pcb, net_server_accept); } extern struct tcp_pcb *tcp_active_pcbs; -void kserver_service(void) +void net_server_service(void) { struct tcp_pcb *pcb; void *data; @@ -208,7 +208,7 @@ void kserver_service(void) session_ack_data(len); } if(len < 0) - kserver_close(active_cs, active_pcb); + net_server_close(active_cs, active_pcb); } } diff --git a/soc/runtime/net_server.h b/soc/runtime/net_server.h new file mode 100644 index 000000000..274f79fe8 --- /dev/null +++ b/soc/runtime/net_server.h @@ -0,0 +1,7 @@ +#ifndef __NET_SERVER_H +#define __NET_SERVER_H + +void net_server_init(void); +void net_server_service(void); + +#endif /* __NET_SERVER_H */ From cd294e2986ffee4ca6b13d5cb5696dfe5c6ded8e Mon Sep 17 00:00:00 2001 From: whitequark Date: Sun, 2 Aug 2015 06:28:58 +0300 Subject: [PATCH 168/369] artiq_personality: avoid unaligned loads. --- soc/runtime/artiq_personality.c | 38 +++++++++++++++++++++++++++------ 1 file changed, 31 insertions(+), 7 deletions(-) diff --git a/soc/runtime/artiq_personality.c b/soc/runtime/artiq_personality.c index 64ee13c31..9bf259474 100644 --- a/soc/runtime/artiq_personality.c +++ b/soc/runtime/artiq_personality.c @@ -126,7 +126,7 @@ static uintptr_t readEncodedPointer(const uint8_t **data, uint8_t encoding) { // first get value switch (encoding & 0x0F) { case DW_EH_PE_absptr: - result = *((uintptr_t*)p); + memcpy(&result, p, sizeof(uintptr_t)); p += sizeof(uintptr_t); break; case DW_EH_PE_uleb128: @@ -137,27 +137,51 @@ static uintptr_t readEncodedPointer(const uint8_t **data, uint8_t encoding) { result = readSLEB128(&p); break; case DW_EH_PE_udata2: - result = *((uint16_t*)p); + { + uint16_t valu16; + memcpy(&valu16, p, sizeof(uint16_t)); + result = valu16; + } p += sizeof(uint16_t); break; case DW_EH_PE_udata4: - result = *((uint32_t*)p); + { + uint32_t valu32; + memcpy(&valu32, p, sizeof(uint32_t)); + result = valu32; + } p += sizeof(uint32_t); break; case DW_EH_PE_udata8: - result = *((uint64_t*)p); + { + uint64_t valu64; + memcpy(&valu64, p, sizeof(uint64_t)); + result = valu64; + } p += sizeof(uint64_t); break; case DW_EH_PE_sdata2: - result = *((int16_t*)p); + { + int16_t val16; + memcpy(&val16, p, sizeof(int16_t)); + result = val16; + } p += sizeof(int16_t); break; case DW_EH_PE_sdata4: - result = *((int32_t*)p); + { + int32_t val32; + memcpy(&val32, p, sizeof(int32_t)); + result = val32; + } p += sizeof(int32_t); break; case DW_EH_PE_sdata8: - result = *((int64_t*)p); + { + int64_t val64; + memcpy(&val64, p, sizeof(int64_t)); + result = val64; + } p += sizeof(int64_t); break; default: From aae2923c4c71b2745af8b9542729c4f5960794ab Mon Sep 17 00:00:00 2001 From: whitequark Date: Sun, 2 Aug 2015 06:33:12 +0300 Subject: [PATCH 169/369] runtime: add lognonl{,_va} functions. The kernels have print(), which prints aggregates (such as arrays) piece-by-piece, and newlines would interfere. --- artiq/compiler/targets.py | 2 +- soc/runtime/kloader.c | 6 +++++- soc/runtime/ksupport.c | 13 +++++++++++++ soc/runtime/log.c | 22 ++++++++++++++++++---- soc/runtime/log.h | 3 +++ soc/runtime/messages.h | 1 + 6 files changed, 41 insertions(+), 6 deletions(-) diff --git a/artiq/compiler/targets.py b/artiq/compiler/targets.py index e0715674b..1b89e039e 100644 --- a/artiq/compiler/targets.py +++ b/artiq/compiler/targets.py @@ -83,4 +83,4 @@ class NativeTarget(Target): class OR1KTarget(Target): triple = "or1k-linux" attributes = ["mul", "div", "ffl1", "cmov", "addc"] - print_function = "log" + print_function = "lognonl" diff --git a/soc/runtime/kloader.c b/soc/runtime/kloader.c index 0cdf56634..136fd7cbb 100644 --- a/soc/runtime/kloader.c +++ b/soc/runtime/kloader.c @@ -175,7 +175,11 @@ void kloader_service_essential_kmsg(void) case MESSAGE_TYPE_LOG: { struct msg_log *msg = (struct msg_log *)umsg; - log_va(msg->fmt, msg->args); + if(msg->no_newline) { + lognonl_va(msg->fmt, msg->args); + } else { + log_va(msg->fmt, msg->args); + } mailbox_acknowledge(); break; } diff --git a/soc/runtime/ksupport.c b/soc/runtime/ksupport.c index eb7a784b7..8e8677059 100644 --- a/soc/runtime/ksupport.c +++ b/soc/runtime/ksupport.c @@ -147,12 +147,25 @@ int rpc(int rpc_num, ...) return retval; } +void lognonl(const char *fmt, ...) +{ + struct msg_log request; + + request.type = MESSAGE_TYPE_LOG; + request.fmt = fmt; + request.no_newline = 1; + va_start(request.args, fmt); + mailbox_send_and_wait(&request); + va_end(request.args); +} + void log(const char *fmt, ...) { struct msg_log request; request.type = MESSAGE_TYPE_LOG; request.fmt = fmt; + request.no_newline = 0; va_start(request.args, fmt); mailbox_send_and_wait(&request); va_end(request.args); diff --git a/soc/runtime/log.c b/soc/runtime/log.c index 4f1750f2f..ebc7990ec 100644 --- a/soc/runtime/log.c +++ b/soc/runtime/log.c @@ -1,5 +1,6 @@ #include #include +#include #include @@ -8,7 +9,7 @@ static int buffer_index; static char buffer[LOG_BUFFER_SIZE]; -void log_va(const char *fmt, va_list args) +void lognonl_va(const char *fmt, va_list args) { char outbuf[256]; int i, len; @@ -18,16 +19,29 @@ void log_va(const char *fmt, va_list args) buffer[buffer_index] = outbuf[i]; buffer_index = (buffer_index + 1) % LOG_BUFFER_SIZE; } - buffer[buffer_index] = '\n'; - buffer_index = (buffer_index + 1) % LOG_BUFFER_SIZE; #ifdef CSR_ETHMAC_BASE /* Since main comms are over ethernet, the serial port * is free for us to use. */ - puts(outbuf); + putsnonl(outbuf); #endif } +void lognonl(const char *fmt, ...) +{ + va_list args; + + va_start(args, fmt); + lognonl_va(fmt, args); + va_end(args); +} + +void log_va(const char *fmt, va_list args) +{ + lognonl_va(fmt, args); + lognonl("\n"); +} + void log(const char *fmt, ...) { va_list args; diff --git a/soc/runtime/log.h b/soc/runtime/log.h index 814155b1c..0fb7ce8b1 100644 --- a/soc/runtime/log.h +++ b/soc/runtime/log.h @@ -5,6 +5,9 @@ #define LOG_BUFFER_SIZE 4096 +void lognonl_va(const char *fmt, va_list args); +void lognonl(const char *fmt, ...); + void log_va(const char *fmt, va_list args); void log(const char *fmt, ...); diff --git a/soc/runtime/messages.h b/soc/runtime/messages.h index 221b394a3..b0685105d 100644 --- a/soc/runtime/messages.h +++ b/soc/runtime/messages.h @@ -80,6 +80,7 @@ struct msg_rpc_reply { struct msg_log { int type; const char *fmt; + int no_newline; va_list args; }; From 6db93b34e8c56e93a4438cbbeabf28cf07dcd014 Mon Sep 17 00:00:00 2001 From: whitequark Date: Sun, 2 Aug 2015 06:34:11 +0300 Subject: [PATCH 170/369] artiq_personality: port to device. --- soc/runtime/artiq_personality.c | 9 +++++++++ soc/runtime/artiq_personality.h | 3 ++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/soc/runtime/artiq_personality.c b/soc/runtime/artiq_personality.c index 9bf259474..9220c0e38 100644 --- a/soc/runtime/artiq_personality.c +++ b/soc/runtime/artiq_personality.c @@ -8,8 +8,14 @@ /* Logging */ #ifndef NDEBUG +#if defined(__or1k__) +#include "log.h" +#define EH_LOG0(fmt) log("%s: " fmt, __func__) +#define EH_LOG(fmt, ...) log("%s: " fmt, __func__, __VA_ARGS__) +#else #define EH_LOG0(fmt) fprintf(stderr, "%s: " fmt "\n", __func__) #define EH_LOG(fmt, ...) fprintf(stderr, "%s: " fmt "\n", __func__, __VA_ARGS__) +#endif #else #define EH_LOG0(fmt) #define EH_LOG(fmt, ...) @@ -312,6 +318,9 @@ static _Unwind_Reason_Code __artiq_uncaught_exception( } } +_Unwind_Reason_Code __artiq_personality( + int version, _Unwind_Action actions, uint64_t exceptionClass, + struct _Unwind_Exception *exceptionObject, struct _Unwind_Context *context); _Unwind_Reason_Code __artiq_personality( int version, _Unwind_Action actions, uint64_t exceptionClass, struct _Unwind_Exception *exceptionObject, struct _Unwind_Context *context) { diff --git a/soc/runtime/artiq_personality.h b/soc/runtime/artiq_personality.h index fec789d27..1952e7269 100644 --- a/soc/runtime/artiq_personality.h +++ b/soc/runtime/artiq_personality.h @@ -2,6 +2,7 @@ #define __ARTIQ_PERSONALITY_H #include +#include struct artiq_exception { union { @@ -27,7 +28,7 @@ extern "C" { /* Provided by the runtime */ void __artiq_raise(struct artiq_exception *artiq_exn) __attribute__((noreturn)); -void __artiq_reraise() +void __artiq_reraise(void) __attribute__((noreturn)); #define artiq_raise_from_c(exnname, exnmsg, exnparam0, exnparam1, exnparam2) \ From 62fdc75d2d64fe53e6129bb97bfbc6ee3799af54 Mon Sep 17 00:00:00 2001 From: whitequark Date: Sun, 2 Aug 2015 06:41:05 +0300 Subject: [PATCH 171/369] Integrate libdyld and libunwind. It is currently possible to run the idle experiment, and it can raise and catch exceptions, but exceptions are not yet propagated across RPC boundaries. --- artiq/coredevice/comm_generic.py | 4 +- soc/runtime/Makefile | 35 +-- soc/runtime/artiq_personality.c | 4 +- soc/runtime/bridge.c | 2 +- soc/runtime/dds.c | 8 +- soc/runtime/elf_loader.c | 240 -------------------- soc/runtime/elf_loader.h | 16 -- soc/runtime/exception_jmp.S | 37 ---- soc/runtime/exceptions.c | 58 ----- soc/runtime/exceptions.h | 25 --- soc/runtime/gen_service_table.py | 65 ------ soc/runtime/kloader.c | 140 +++++------- soc/runtime/kloader.h | 15 +- soc/runtime/ksupport.c | 305 +++++++++++++++++++++----- soc/runtime/ksupport.h | 12 + soc/runtime/ksupport.ld | 27 ++- soc/runtime/messages.h | 20 +- soc/runtime/rtio.h | 8 +- soc/runtime/{linker.ld => runtime.ld} | 20 +- soc/runtime/services.c | 78 ------- soc/runtime/services.h | 6 - soc/runtime/session.c | 35 ++- soc/runtime/ttl.c | 5 +- 23 files changed, 433 insertions(+), 732 deletions(-) delete mode 100644 soc/runtime/elf_loader.c delete mode 100644 soc/runtime/elf_loader.h delete mode 100644 soc/runtime/exception_jmp.S delete mode 100644 soc/runtime/exceptions.c delete mode 100644 soc/runtime/exceptions.h delete mode 100755 soc/runtime/gen_service_table.py create mode 100644 soc/runtime/ksupport.h rename soc/runtime/{linker.ld => runtime.ld} (71%) delete mode 100644 soc/runtime/services.c delete mode 100644 soc/runtime/services.h diff --git a/artiq/coredevice/comm_generic.py b/artiq/coredevice/comm_generic.py index 88fee184b..2daa24cdc 100644 --- a/artiq/coredevice/comm_generic.py +++ b/artiq/coredevice/comm_generic.py @@ -16,7 +16,7 @@ class _H2DMsgType(Enum): IDENT_REQUEST = 2 SWITCH_CLOCK = 3 - LOAD_OBJECT = 4 + LOAD_LIBRARY = 4 RUN_KERNEL = 5 RPC_REPLY = 6 @@ -124,7 +124,7 @@ class CommGeneric: raise IOError("Incorrect reply from device: {}".format(ty)) def load(self, kcode): - self._write_header(len(kcode) + 9, _H2DMsgType.LOAD_OBJECT) + self._write_header(len(kcode) + 9, _H2DMsgType.LOAD_LIBRARY) self.write(kcode) _, ty = self._read_header() if ty != _D2HMsgType.LOAD_COMPLETED: diff --git a/soc/runtime/Makefile b/soc/runtime/Makefile index 36fb42b2b..acab0e96e 100644 --- a/soc/runtime/Makefile +++ b/soc/runtime/Makefile @@ -1,9 +1,15 @@ include $(MSCDIR)/software/common.mak -OBJECTS := isr.o flash_storage.o clock.o rtiocrg.o elf_loader.o services.o session.o log.o test_mode.o kloader.o bridge_ctl.o mailbox.o ksupport_data.o net_server.o moninj.o main.o -OBJECTS_KSUPPORT := ksupport.o exception_jmp.o exceptions.o mailbox.o bridge.o rtio.o ttl.o dds.o +OBJECTS := isr.o clock.o rtiocrg.o flash_storage.o mailbox.o \ + session.o log.o moninj.o net_server.o bridge_ctl.o \ + ksupport_data.o kloader.o test_mode.o main.o +OBJECTS_KSUPPORT := ksupport.o artiq_personality.o mailbox.o \ + bridge.o rtio.o ttl.o dds.o -CFLAGS += -Ilwip/src/include -Iliblwip +CFLAGS += -I$(MSCDIR)/software/include/dyld \ + -I$(MSCDIR)/software/unwinder/include \ + -I$(MSCDIR)/software/libunwind \ + -Ilwip/src/include -Iliblwip all: runtime.bin runtime.fbi @@ -19,7 +25,7 @@ all: runtime.bin runtime.fbi runtime.elf: $(OBJECTS) libs $(LD) $(LDFLAGS) \ - -T linker.ld \ + -T runtime.ld \ -N -o $@ \ $(MSCDIR)/software/libbase/crt0-$(CPU).o \ $(OBJECTS) \ @@ -29,24 +35,23 @@ runtime.elf: $(OBJECTS) libs -lbase -lcompiler-rt -llwip @chmod -x $@ -ksupport.elf: $(OBJECTS_KSUPPORT) +ksupport.elf: $(OBJECTS_KSUPPORT) libs $(LD) $(LDFLAGS) \ + --eh-frame-hdr \ -T ksupport.ld \ -N -o $@ \ $(MSCDIR)/software/libbase/crt0-$(CPU).o \ - $^ \ + $(OBJECTS_KSUPPORT) \ + -L$(MSCDIR)/software/libbase \ -L$(MSCDIR)/software/libcompiler-rt \ - -lcompiler-rt + -L$(MSCDIR)/software/libunwind \ + -L$(MSCDIR)/software/libdyld \ + -lbase -lcompiler-rt -lunwind -ldyld @chmod -x $@ -ksupport_data.o: ksupport.bin +ksupport_data.o: ksupport.elf $(LD) -r -b binary -o $@ $< -service_table.h: ksupport.elf gen_service_table.py - @echo " GEN " $@ && ./gen_service_table.py ksupport.elf > $@ - -services.c: service_table.h - main.o: main.c $(compile-dep) @@ -58,13 +63,15 @@ main.o: main.c libs: $(MAKE) -C $(MSCDIR)/software/libcompiler-rt + $(MAKE) -C $(MSCDIR)/software/libunwind $(MAKE) -C $(MSCDIR)/software/libbase + $(MAKE) -C $(MSCDIR)/software/libdyld $(MAKE) -C liblwip clean: $(MAKE) -C liblwip clean $(RM) $(OBJECTS) $(OBJECTS:.o=.d) $(OBJECTS_KSUPPORT) $(OBJECTS_KSUPPORT:.o=.d) $(RM) runtime.elf runtime.bin runtime.fbi .*~ *~ - $(RM) service_table.h ksupport.elf ksupport.bin + $(RM) ksupport.elf ksupport.bin .PHONY: all main.o clean libs load diff --git a/soc/runtime/artiq_personality.c b/soc/runtime/artiq_personality.c index 9220c0e38..2dc2350a5 100644 --- a/soc/runtime/artiq_personality.c +++ b/soc/runtime/artiq_personality.c @@ -246,7 +246,9 @@ struct artiq_raised_exception { static struct artiq_raised_exception inflight; void __artiq_raise(struct artiq_exception *artiq_exn) { - EH_LOG("===> raise (name=%s)", artiq_exn->name); + EH_LOG("===> raise (name=%s, msg=%s, params=[%lld,%lld,%lld])", + artiq_exn->name, artiq_exn->message, + artiq_exn->param[0], artiq_exn->param[1], artiq_exn->param[2]); memmove(&inflight.artiq, artiq_exn, sizeof(struct artiq_exception)); inflight.unwind.exception_class = ARTIQ_EXCEPTION_CLASS; diff --git a/soc/runtime/bridge.c b/soc/runtime/bridge.c index 78083a16f..c371f6232 100644 --- a/soc/runtime/bridge.c +++ b/soc/runtime/bridge.c @@ -32,7 +32,7 @@ static void send_ready(void) struct msg_base msg; msg.type = MESSAGE_TYPE_BRG_READY; - mailbox_send_and_wait(&msg); + mailbox_send_and_wait(&msg); } void bridge_main(void) diff --git a/soc/runtime/dds.c b/soc/runtime/dds.c index 5f9f8650e..de744e624 100644 --- a/soc/runtime/dds.c +++ b/soc/runtime/dds.c @@ -1,7 +1,7 @@ #include #include -#include "exceptions.h" +#include "artiq_personality.h" #include "rtio.h" #include "log.h" #include "dds.h" @@ -179,7 +179,7 @@ static struct dds_set_params batch[DDS_MAX_BATCH]; void dds_batch_enter(long long int timestamp) { if(batch_mode) - exception_raise(EID_DDS_BATCH_ERROR); + artiq_raise_from_c("DDSBatchError", "DDS batch error", 0, 0, 0); batch_mode = 1; batch_count = 0; batch_ref_time = timestamp; @@ -191,7 +191,7 @@ void dds_batch_exit(void) int i; if(!batch_mode) - exception_raise(EID_DDS_BATCH_ERROR); + artiq_raise_from_c("DDSBatchError", "DDS batch error", 0, 0, 0); rtio_chan_sel_write(RTIO_DDS_CHANNEL); /* + FUD time */ now = batch_ref_time - batch_count*(DURATION_PROGRAM + DURATION_WRITE); @@ -208,7 +208,7 @@ void dds_set(long long int timestamp, int channel, { if(batch_mode) { if(batch_count >= DDS_MAX_BATCH) - exception_raise(EID_DDS_BATCH_ERROR); + artiq_raise_from_c("DDSBatchError", "DDS batch error", 0, 0, 0); /* timestamp parameter ignored (determined by batch) */ batch[batch_count].channel = channel; batch[batch_count].ftw = ftw; diff --git a/soc/runtime/elf_loader.c b/soc/runtime/elf_loader.c deleted file mode 100644 index 8604381e8..000000000 --- a/soc/runtime/elf_loader.c +++ /dev/null @@ -1,240 +0,0 @@ -#include - -#include "log.h" -#include "elf_loader.h" - -#define EI_NIDENT 16 - -struct elf32_ehdr { - unsigned char ident[EI_NIDENT]; /* ident bytes */ - unsigned short type; /* file type */ - unsigned short machine; /* target machine */ - unsigned int version; /* file version */ - unsigned int entry; /* start address */ - unsigned int phoff; /* phdr file offset */ - unsigned int shoff; /* shdr file offset */ - unsigned int flags; /* file flags */ - unsigned short ehsize; /* sizeof ehdr */ - unsigned short phentsize; /* sizeof phdr */ - unsigned short phnum; /* number phdrs */ - unsigned short shentsize; /* sizeof shdr */ - unsigned short shnum; /* number shdrs */ - unsigned short shstrndx; /* shdr string index */ -} __attribute__((packed)); - -static const unsigned char elf_magic_header[] = { - 0x7f, 0x45, 0x4c, 0x46, /* 0x7f, 'E', 'L', 'F' */ - 0x01, /* Only 32-bit objects. */ - 0x02, /* Only big-endian. */ - 0x01, /* Only ELF version 1. */ -}; - -#define ET_NONE 0 /* Unknown type. */ -#define ET_REL 1 /* Relocatable. */ -#define ET_EXEC 2 /* Executable. */ -#define ET_DYN 3 /* Shared object. */ -#define ET_CORE 4 /* Core file. */ - -#define EM_OR1K 0x005c - -struct elf32_shdr { - unsigned int name; /* section name */ - unsigned int type; /* SHT_... */ - unsigned int flags; /* SHF_... */ - unsigned int addr; /* virtual address */ - unsigned int offset; /* file offset */ - unsigned int size; /* section size */ - unsigned int link; /* misc info */ - unsigned int info; /* misc info */ - unsigned int addralign; /* memory alignment */ - unsigned int entsize; /* entry size if table */ -} __attribute__((packed)); - -struct elf32_name { - char name[12]; -} __attribute__((packed)); - -struct elf32_rela { - unsigned int offset; /* Location to be relocated. */ - unsigned int info; /* Relocation type and symbol index. */ - int addend; /* Addend. */ -} __attribute__((packed)); - -#define ELF32_R_SYM(info) ((info) >> 8) -#define ELF32_R_TYPE(info) ((unsigned char)(info)) - -#define R_OR1K_INSN_REL_26 6 - -struct elf32_sym { - unsigned int name; /* String table index of name. */ - unsigned int value; /* Symbol value. */ - unsigned int size; /* Size of associated object. */ - unsigned char info; /* Type and binding information. */ - unsigned char other; /* Reserved (not used). */ - unsigned short shndx; /* Section index of symbol. */ -} __attribute__((packed)); - -#define STT_NOTYPE 0 -#define STT_OBJECT 1 -#define STT_FUNC 2 -#define STT_SECTION 3 -#define STT_FILE 4 - -#define ELF32_ST_TYPE(info) ((info) & 0x0f) - - -#define SANITIZE_OFFSET_SIZE(offset, size) \ - if(offset > 0x10000000) { \ - log("Incorrect offset in ELF data"); \ - return 0; \ - } \ - if((offset + size) > elf_length) { \ - log("Attempted to access past the end of ELF data"); \ - return 0; \ - } - -#define GET_POINTER_SAFE(target, target_type, offset) \ - SANITIZE_OFFSET_SIZE(offset, sizeof(target_type)); \ - target = (target_type *)((char *)elf_data + offset) - -void *find_symbol(const struct symbol *symbols, const char *name) -{ - int i; - - i = 0; - while((symbols[i].name != NULL) && (strcmp(symbols[i].name, name) != 0)) - i++; - return symbols[i].target; -} - -static int fixup(void *dest, int dest_length, struct elf32_rela *rela, void *target) -{ - int type, offset; - unsigned int *_dest = dest; - unsigned int *_target = target; - - type = ELF32_R_TYPE(rela->info); - offset = rela->offset/4; - if(type == R_OR1K_INSN_REL_26) { - int val; - - val = _target - (_dest + offset); - _dest[offset] = (_dest[offset] & 0xfc000000) | (val & 0x03ffffff); - } else - log("Unsupported relocation type: %d", type); - return 1; -} - -int load_elf(symbol_resolver resolver, symbol_callback callback, void *elf_data, int elf_length, void *dest, int dest_length) -{ - struct elf32_ehdr *ehdr; - struct elf32_shdr *strtable; - unsigned int shdrptr; - int i; - - unsigned int textoff, textsize; - unsigned int textrelaoff, textrelasize; - unsigned int symtaboff, symtabsize; - unsigned int strtaboff, strtabsize; - - - /* validate ELF */ - GET_POINTER_SAFE(ehdr, struct elf32_ehdr, 0); - if(memcmp(ehdr->ident, elf_magic_header, sizeof(elf_magic_header)) != 0) { - log("Incorrect ELF header"); - return 0; - } - if(ehdr->type != ET_REL) { - log("ELF is not relocatable"); - return 0; - } - if(ehdr->machine != EM_OR1K) { - log("ELF is for a different machine"); - return 0; - } - - /* extract section info */ - GET_POINTER_SAFE(strtable, struct elf32_shdr, ehdr->shoff + ehdr->shentsize*ehdr->shstrndx); - textoff = textsize = 0; - textrelaoff = textrelasize = 0; - symtaboff = symtabsize = 0; - strtaboff = strtabsize = 0; - shdrptr = ehdr->shoff; - for(i=0;ishnum;i++) { - struct elf32_shdr *shdr; - struct elf32_name *name; - - GET_POINTER_SAFE(shdr, struct elf32_shdr, shdrptr); - GET_POINTER_SAFE(name, struct elf32_name, strtable->offset + shdr->name); - - if(strncmp(name->name, ".text", 5) == 0) { - textoff = shdr->offset; - textsize = shdr->size; - } else if(strncmp(name->name, ".rela.text", 10) == 0) { - textrelaoff = shdr->offset; - textrelasize = shdr->size; - } else if(strncmp(name->name, ".symtab", 7) == 0) { - symtaboff = shdr->offset; - symtabsize = shdr->size; - } else if(strncmp(name->name, ".strtab", 7) == 0) { - strtaboff = shdr->offset; - strtabsize = shdr->size; - } - - shdrptr += ehdr->shentsize; - } - SANITIZE_OFFSET_SIZE(textoff, textsize); - SANITIZE_OFFSET_SIZE(textrelaoff, textrelasize); - SANITIZE_OFFSET_SIZE(symtaboff, symtabsize); - SANITIZE_OFFSET_SIZE(strtaboff, strtabsize); - - /* load .text section */ - if(textsize > dest_length) { - log(".text section is too large"); - return 0; - } - memcpy(dest, (char *)elf_data + textoff, textsize); - - /* process .text relocations */ - for(i=0;iinfo)); - if(sym->name != 0) { - char *name; - void *target; - - name = (char *)elf_data + strtaboff + sym->name; - target = resolver(name); - if(target == NULL) { - log("Undefined symbol: %s", name); - return 0; - } - if(!fixup(dest, dest_length, rela, target)) - return 0; - } else { - log("Unsupported relocation"); - return 0; - } - } - - /* list provided functions via callback */ - for(i=0;iinfo) == STT_FUNC) && (sym->name != 0)) { - char *name; - void *target; - - name = (char *)elf_data + strtaboff + sym->name; - target = (char *)dest + sym->value; - if(!callback(name, target)) - return 0; - } - } - - return 1; -} diff --git a/soc/runtime/elf_loader.h b/soc/runtime/elf_loader.h deleted file mode 100644 index a116e0851..000000000 --- a/soc/runtime/elf_loader.h +++ /dev/null @@ -1,16 +0,0 @@ -#ifndef __ELF_LOADER_H -#define __ELF_LOADER_H - -struct symbol { - char *name; - void *target; -}; - -typedef void * (*symbol_resolver)(const char *); -typedef int (*symbol_callback)(const char *, void *); - -void *find_symbol(const struct symbol *symbols, const char *name); -/* elf_data must be aligned on a 32-bit boundary */ -int load_elf(symbol_resolver resolver, symbol_callback callback, void *elf_data, int elf_length, void *dest, int dest_length); - -#endif /* __ELF_LOADER_H */ diff --git a/soc/runtime/exception_jmp.S b/soc/runtime/exception_jmp.S deleted file mode 100644 index 014422960..000000000 --- a/soc/runtime/exception_jmp.S +++ /dev/null @@ -1,37 +0,0 @@ -.global exception_setjmp -.type exception_setjmp, @function -exception_setjmp: - l.sw 0(r3), r1 - l.sw 4(r3), r2 - l.sw 8(r3), r9 - l.sw 12(r3), r10 - l.sw 16(r3), r14 - l.sw 20(r3), r16 - l.sw 24(r3), r18 - l.sw 28(r3), r20 - l.sw 32(r3), r22 - l.sw 36(r3), r24 - l.sw 40(r3), r26 - l.sw 44(r3), r28 - l.sw 48(r3), r30 - l.jr r9 - l.ori r11, r0, 0 - -.global exception_longjmp -.type exception_longjmp, @function -exception_longjmp: - l.lwz r1, 0(r3) - l.lwz r2, 4(r3) - l.lwz r9, 8(r3) - l.lwz r10, 12(r3) - l.lwz r14, 16(r3) - l.lwz r16, 20(r3) - l.lwz r18, 24(r3) - l.lwz r20, 28(r3) - l.lwz r22, 32(r3) - l.lwz r24, 36(r3) - l.lwz r26, 40(r3) - l.lwz r28, 44(r3) - l.lwz r30, 48(r3) - l.jr r9 - l.ori r11, r0, 1 diff --git a/soc/runtime/exceptions.c b/soc/runtime/exceptions.c deleted file mode 100644 index 5c82f5c43..000000000 --- a/soc/runtime/exceptions.c +++ /dev/null @@ -1,58 +0,0 @@ -#include - -#include "log.h" -#include "exceptions.h" - -#define MAX_EXCEPTION_CONTEXTS 64 - -struct exception_context { - void *jb[13]; -}; - -static struct exception_context exception_contexts[MAX_EXCEPTION_CONTEXTS]; -static int ec_top; -static int stored_id; -static long long int stored_params[3]; - -void *exception_push(void) -{ - if(ec_top >= MAX_EXCEPTION_CONTEXTS) - exception_raise(EID_INTERNAL_ERROR); - return exception_contexts[ec_top++].jb; -} - -void exception_pop(int levels) -{ - ec_top -= levels; -} - -int exception_getid(long long int *eparams) -{ - int i; - - if(eparams) - for(i=0;i<3;i++) - eparams[i] = stored_params[i]; - return stored_id; -} - -void exception_raise(int id) -{ - exception_raise_params(id, 0, 0, 0); -} - -void exception_raise_params(int id, - long long int p0, long long int p1, - long long int p2) -{ - if(ec_top > 0) { - stored_id = id; - stored_params[0] = p0; - stored_params[1] = p1; - stored_params[2] = p2; - exception_longjmp(exception_contexts[--ec_top].jb); - } else { - log("ERROR: uncaught exception, ID=%d\n", id); - while(1); - } -} diff --git a/soc/runtime/exceptions.h b/soc/runtime/exceptions.h deleted file mode 100644 index 1c820c060..000000000 --- a/soc/runtime/exceptions.h +++ /dev/null @@ -1,25 +0,0 @@ -#ifndef __EXCEPTIONS_H -#define __EXCEPTIONS_H - -enum { - EID_NONE = 0, - EID_INTERNAL_ERROR = 1, - EID_RPC_EXCEPTION = 2, - EID_RTIO_UNDERFLOW = 3, - EID_RTIO_SEQUENCE_ERROR = 4, - EID_RTIO_OVERFLOW = 5, - EID_DDS_BATCH_ERROR = 6, -}; - -int exception_setjmp(void *jb) __attribute__((returns_twice)); -void exception_longjmp(void *jb) __attribute__((noreturn)); - -void *exception_push(void); -void exception_pop(int levels); -int exception_getid(long long int *eparams); -void exception_raise(int id) __attribute__((noreturn)); -void exception_raise_params(int id, - long long int p0, long long int p1, - long long int p2) __attribute__((noreturn)); - -#endif /* __EXCEPTIONS_H */ diff --git a/soc/runtime/gen_service_table.py b/soc/runtime/gen_service_table.py deleted file mode 100755 index 27ae562b0..000000000 --- a/soc/runtime/gen_service_table.py +++ /dev/null @@ -1,65 +0,0 @@ -#!/usr/bin/env python3 - -import sys - -from elftools.elf.elffile import ELFFile - - -services = [ - ("syscalls", [ - ("now_init", "now_init"), - ("now_save", "now_save"), - - ("watchdog_set", "watchdog_set"), - ("watchdog_clear", "watchdog_clear"), - - ("rpc", "rpc"), - - ("rtio_get_counter", "rtio_get_counter"), - - ("ttl_set_o", "ttl_set_o"), - ("ttl_set_oe", "ttl_set_oe"), - ("ttl_set_sensitivity", "ttl_set_sensitivity"), - ("ttl_get", "ttl_get"), - ("ttl_clock_set", "ttl_clock_set"), - - ("dds_init", "dds_init"), - ("dds_batch_enter", "dds_batch_enter"), - ("dds_batch_exit", "dds_batch_exit"), - ("dds_set", "dds_set"), - ]), - - ("eh", [ - ("setjmp", "exception_setjmp"), - ("push", "exception_push"), - ("pop", "exception_pop"), - ("getid", "exception_getid"), - ("raise", "exception_raise"), - ]) -] - - -def print_service_table(ksupport_elf_filename): - with open(ksupport_elf_filename, "rb") as f: - elf = ELFFile(f) - symtab = elf.get_section_by_name(b".symtab") - symbols = {symbol.name: symbol.entry.st_value - for symbol in symtab.iter_symbols()} - for name, contents in services: - print("static const struct symbol {}[] = {{".format(name)) - for name, value in contents: - print(" {{\"{}\", (void *)0x{:08x}}}," - .format(name, symbols[bytes(value, "ascii")])) - print(" {NULL, NULL}") - print("};") - - -def main(): - if len(sys.argv) == 2: - print_service_table(sys.argv[1]) - else: - print("Incorrect number of command line arguments") - sys.exit(1) - -if __name__ == "__main__": - main() diff --git a/soc/runtime/kloader.c b/soc/runtime/kloader.c index 136fd7cbb..ea8e4f5df 100644 --- a/soc/runtime/kloader.c +++ b/soc/runtime/kloader.c @@ -1,120 +1,82 @@ #include #include +#include + +#include "kloader.h" #include "log.h" #include "flash_storage.h" #include "mailbox.h" #include "messages.h" -#include "elf_loader.h" -#include "services.h" -#include "kloader.h" -static struct symbol symtab[128]; -static int _symtab_count; -static char _symtab_strings[128*16]; -static char *_symtab_strptr; - -static void symtab_init(void) +static void start_kernel_cpu(struct msg_load_request *msg) { - memset(symtab, 0, sizeof(symtab)); - _symtab_count = 0; - _symtab_strptr = _symtab_strings; + // Stop kernel CPU before messing with its code. + kernel_cpu_reset_write(1); + + // Load kernel support code. + extern void _binary_ksupport_elf_start, _binary_ksupport_elf_end; + memcpy((void *)(KERNELCPU_EXEC_ADDRESS - KSUPPORT_HEADER_SIZE), + &_binary_ksupport_elf_start, + &_binary_ksupport_elf_end - &_binary_ksupport_elf_start); + + // Start kernel CPU. + mailbox_send(msg); + kernel_cpu_reset_write(0); } -static int symtab_add(const char *name, void *target) +void kloader_start_bridge() { - if(_symtab_count >= sizeof(symtab)/sizeof(symtab[0])) { - log("Too many provided symbols in object"); - symtab_init(); - return 0; - } - symtab[_symtab_count].name = _symtab_strptr; - symtab[_symtab_count].target = target; - _symtab_count++; + start_kernel_cpu(NULL); +} - while(1) { - if(_symtab_strptr >= &_symtab_strings[sizeof(_symtab_strings)]) { - log("Provided symbol string table overflow"); - symtab_init(); - return 0; - } - *_symtab_strptr = *name; - _symtab_strptr++; - if(*name == 0) - break; - name++; +static int load_or_start_kernel(void *library, const char *kernel) +{ + static struct dyld_info library_info; + struct msg_load_request request = { + .library = library, + .library_info = &library_info, + .kernel = kernel, + }; + start_kernel_cpu(&request); + + struct msg_load_reply *reply = mailbox_wait_and_receive(); + if(reply != NULL && reply->type == MESSAGE_TYPE_LOAD_REPLY) { + log("cannot load/run kernel: %s", reply->error); + return 0; } return 1; } -int kloader_load(void *buffer, int length) +int kloader_load_library(void *library) { if(!kernel_cpu_reset_read()) { - log("BUG: attempted to load while kernel CPU running"); + log("BUG: attempted to load kernel library while kernel CPU is running"); return 0; } - symtab_init(); - return load_elf( - resolve_service_symbol, symtab_add, - buffer, length, (void *)KERNELCPU_PAYLOAD_ADDRESS, 4*1024*1024); + + return load_or_start_kernel(library, NULL); } -kernel_function kloader_find(const char *name) +int kloader_start_kernel(const char *name) { - return find_symbol(symtab, name); + return load_or_start_kernel(NULL, name); } -extern char _binary_ksupport_bin_start; -extern char _binary_ksupport_bin_end; - -static void start_kernel_cpu(void *addr) +int kloader_start_idle_kernel(void) { - memcpy((void *)KERNELCPU_EXEC_ADDRESS, &_binary_ksupport_bin_start, - &_binary_ksupport_bin_end - &_binary_ksupport_bin_start); - mailbox_acknowledge(); - mailbox_send(addr); - kernel_cpu_reset_write(0); -} - -void kloader_start_bridge(void) -{ - start_kernel_cpu(NULL); -} - -void kloader_start_user_kernel(kernel_function k) -{ - if(!kernel_cpu_reset_read()) { - log("BUG: attempted to start kernel CPU while already running (user kernel)"); - return; - } - start_kernel_cpu((void *)k); -} - -void kloader_start_idle_kernel(void) -{ - char buffer[32*1024]; - int len; - kernel_function k; - - if(!kernel_cpu_reset_read()) { - log("BUG: attempted to start kernel CPU while already running (idle kernel)"); - return; - } #if (defined CSR_SPIFLASH_BASE && defined SPIFLASH_PAGE_SIZE) - len = fs_read("idle_kernel", buffer, sizeof(buffer), NULL); - if(len <= 0) - return; - if(!kloader_load(buffer, len)) { - log("Failed to load ELF binary for idle kernel"); - return; - } - k = kloader_find("run"); - if(!k) { - log("Failed to find entry point for ELF kernel"); - return; - } - start_kernel_cpu((void *)k); + char buffer[32*1024]; + int length; + + length = fs_read("idle_kernel", buffer, sizeof(buffer), NULL); + if(length <= 0) + return 0; + + return load_or_start_kernel(buffer, "test.__modinit__"); +#else + return 0; #endif } @@ -127,7 +89,7 @@ void kloader_stop(void) int kloader_validate_kpointer(void *p) { unsigned int v = (unsigned int)p; - if((v < 0x40400000) || (v > (0x4fffffff - 1024*1024))) { + if((v < KERNELCPU_EXEC_ADDRESS) || (v > KERNELCPU_LAST_ADDRESS)) { log("Received invalid pointer from kernel CPU: 0x%08x", v); return 0; } diff --git a/soc/runtime/kloader.h b/soc/runtime/kloader.h index ceefbc89d..08036456c 100644 --- a/soc/runtime/kloader.h +++ b/soc/runtime/kloader.h @@ -1,19 +1,18 @@ #ifndef __KLOADER_H #define __KLOADER_H -#define KERNELCPU_EXEC_ADDRESS 0x40400000 -#define KERNELCPU_PAYLOAD_ADDRESS 0x40408000 +#define KERNELCPU_EXEC_ADDRESS 0x40400000 +#define KERNELCPU_PAYLOAD_ADDRESS 0x40420000 +#define KERNELCPU_LAST_ADDRESS (0x4fffffff - 1024*1024) +#define KSUPPORT_HEADER_SIZE 0x80 extern long long int now; -typedef void (*kernel_function)(void); - -int kloader_load(void *buffer, int length); -kernel_function kloader_find(const char *name); +int kloader_load_library(void *code); void kloader_start_bridge(void); -void kloader_start_idle_kernel(void); -void kloader_start_user_kernel(kernel_function k); +int kloader_start_idle_kernel(void); +int kloader_start_kernel(const char *name); void kloader_stop(void); int kloader_validate_kpointer(void *p); diff --git a/soc/runtime/ksupport.c b/soc/runtime/ksupport.c index 8e8677059..c7d465c26 100644 --- a/soc/runtime/ksupport.c +++ b/soc/runtime/ksupport.c @@ -1,67 +1,254 @@ #include +#include +#include -#include "exceptions.h" -#include "bridge.h" +#include +#include +#include +#include + +#include "ksupport.h" +#include "kloader.h" #include "mailbox.h" #include "messages.h" -#include "rtio.h" +#include "bridge.h" +#include "artiq_personality.h" +#include "ttl.h" #include "dds.h" +#include "rtio.h" -/* for the prototypes for watchdog_set() and watchdog_clear() */ -#include "clock.h" -/* for the prototype for rpc() */ -#include "session.h" -/* for the prototype for log() */ -#include "log.h" +/* compiler-rt symbols */ +extern void __divsi3, __modsi3, __ledf2, __gedf2, __unorddf2, __eqdf2, __ltdf2, + __nedf2, __gtdf2, __negsf2, __negdf2, __addsf3, __subsf3, __mulsf3, + __divsf3, __lshrdi3, __muldi3, __divdi3, __ashldi3, __ashrdi3, + __udivmoddi4, __floatsisf, __floatunsisf, __fixsfsi, __fixunssfsi, + __adddf3, __subdf3, __muldf3, __divdf3, __floatsidf, __floatunsidf, + __floatdidf, __fixdfsi, __fixdfdi, __fixunsdfsi, __clzsi2, __ctzsi2, + __udivdi3, __umoddi3, __moddi3; -void exception_handler(unsigned long vect, unsigned long *sp); -void exception_handler(unsigned long vect, unsigned long *sp) +/* artiq_personality symbols */ +extern void __artiq_personality; + +struct symbol { + const char *name; + void *addr; +}; + +static const struct symbol runtime_exports[] = { + /* compiler-rt */ + {"divsi3", &__divsi3}, + {"modsi3", &__modsi3}, + {"ledf2", &__ledf2}, + {"gedf2", &__gedf2}, + {"unorddf2", &__unorddf2}, + {"eqdf2", &__eqdf2}, + {"ltdf2", &__ltdf2}, + {"nedf2", &__nedf2}, + {"gtdf2", &__gtdf2}, + {"negsf2", &__negsf2}, + {"negdf2", &__negdf2}, + {"addsf3", &__addsf3}, + {"subsf3", &__subsf3}, + {"mulsf3", &__mulsf3}, + {"divsf3", &__divsf3}, + {"lshrdi3", &__lshrdi3}, + {"muldi3", &__muldi3}, + {"divdi3", &__divdi3}, + {"ashldi3", &__ashldi3}, + {"ashrdi3", &__ashrdi3}, + {"udivmoddi4", &__udivmoddi4}, + {"floatsisf", &__floatsisf}, + {"floatunsisf", &__floatunsisf}, + {"fixsfsi", &__fixsfsi}, + {"fixunssfsi", &__fixunssfsi}, + {"adddf3", &__adddf3}, + {"subdf3", &__subdf3}, + {"muldf3", &__muldf3}, + {"divdf3", &__divdf3}, + {"floatsidf", &__floatsidf}, + {"floatunsidf", &__floatunsidf}, + {"floatdidf", &__floatdidf}, + {"fixdfsi", &__fixdfsi}, + {"fixdfdi", &__fixdfdi}, + {"fixunsdfsi", &__fixunsdfsi}, + {"clzsi2", &__clzsi2}, + {"ctzsi2", &__ctzsi2}, + {"udivdi3", &__udivdi3}, + {"umoddi3", &__umoddi3}, + {"moddi3", &__moddi3}, + + /* exceptions */ + {"_Unwind_Resume", &_Unwind_Resume}, + {"__artiq_personality", &__artiq_personality}, + {"__artiq_raise", &__artiq_raise}, + {"__artiq_reraise", &__artiq_reraise}, + + /* proxified syscalls */ + {"now_init", &now_init}, + {"now_save", &now_save}, + + {"watchdog_set", &watchdog_set}, + {"watchdog_clear", &watchdog_clear}, + + {"log", &log}, + {"lognonl", &lognonl}, + {"rpc", &rpc}, + + /* direct syscalls */ + {"rtio_get_counter", &rtio_get_counter}, + + {"ttl_set_o", &ttl_set_o}, + {"ttl_set_oe", &ttl_set_oe}, + {"ttl_set_sensitivity", &ttl_set_sensitivity}, + {"ttl_get", &ttl_get}, + {"ttl_clock_set", &ttl_clock_set}, + + {"dds_init", &dds_init}, + {"dds_batch_enter", &dds_batch_enter}, + {"dds_batch_exit", &dds_batch_exit}, + {"dds_set", &dds_set}, + + /* end */ + {NULL, NULL} +}; + +/* called by libunwind */ +int fprintf(FILE *stream, const char *fmt, ...) { - struct msg_exception msg; + struct msg_log request; - msg.type = MESSAGE_TYPE_EXCEPTION; - msg.eid = EID_INTERNAL_ERROR; - msg.eparams[0] = 256; - msg.eparams[1] = 256; - msg.eparams[2] = 256; - mailbox_send_and_wait(&msg); - while(1); + request.type = MESSAGE_TYPE_LOG; + request.fmt = fmt; + request.no_newline = 1; + va_start(request.args, fmt); + mailbox_send_and_wait(&request); + va_end(request.args); + + return 0; } -typedef void (*kernel_function)(void); +/* called by libunwind */ +int dladdr (const void *address, Dl_info *info) { + /* we don't try to resolve names */ + return 0; +} + +/* called by libunwind */ +int dl_iterate_phdr (int (*callback) (struct dl_phdr_info *, size_t, void *), void *data) { + Elf32_Ehdr *ehdr; + struct dl_phdr_info phdr_info; + int retval; + + ehdr = (Elf32_Ehdr *)(KERNELCPU_EXEC_ADDRESS - KSUPPORT_HEADER_SIZE); + phdr_info = (struct dl_phdr_info){ + .dlpi_addr = 0, /* absolutely linked */ + .dlpi_name = "", + .dlpi_phdr = (Elf32_Phdr*) ((intptr_t)ehdr + ehdr->e_phoff), + .dlpi_phnum = ehdr->e_phnum, + }; + retval = callback(&phdr_info, sizeof(phdr_info), data); + if(retval) + return retval; + + ehdr = (Elf32_Ehdr *)KERNELCPU_PAYLOAD_ADDRESS; + phdr_info = (struct dl_phdr_info){ + .dlpi_addr = KERNELCPU_PAYLOAD_ADDRESS, + .dlpi_name = "", + .dlpi_phdr = (Elf32_Phdr*) ((intptr_t)ehdr + ehdr->e_phoff), + .dlpi_phnum = ehdr->e_phnum, + }; + retval = callback(&phdr_info, sizeof(phdr_info), data); + return retval; +} + +static Elf32_Addr resolve_runtime_export(const char *name) { + const struct symbol *sym = runtime_exports; + while(sym->name) { + if(!strcmp(sym->name, name)) + return (Elf32_Addr)sym->addr; + ++sym; + } + return 0; +} + +void exception_handler(unsigned long vect, unsigned long *regs, + unsigned long pc, unsigned long ea); +void exception_handler(unsigned long vect, unsigned long *regs, + unsigned long pc, unsigned long ea) +{ + artiq_raise_from_c("InternalError", + "Hardware exception {0} at PC {1}, EA {2}", + vect, pc, ea); +} int main(void); int main(void) { - kernel_function k; - void *jb; + struct msg_load_request *msg = mailbox_receive(); - k = mailbox_receive(); - - if(k == NULL) + if(msg == NULL) { bridge_main(); - else { - jb = exception_push(); - if(exception_setjmp(jb)) { - struct msg_exception msg; + while(1); + } - msg.type = MESSAGE_TYPE_EXCEPTION; - msg.eid = exception_getid(msg.eparams); - mailbox_send_and_wait(&msg); - } else { - struct msg_base msg; - - k(); - exception_pop(1); - - msg.type = MESSAGE_TYPE_FINISHED; - mailbox_send_and_wait(&msg); + if(msg->library != NULL) { + const char *error; + if(!dyld_load(msg->library, KERNELCPU_PAYLOAD_ADDRESS, + resolve_runtime_export, msg->library_info, &error)) { + struct msg_load_reply msg = { + .type = MESSAGE_TYPE_LOAD_REPLY, + .error = error + }; + mailbox_send(&msg); + while(1); } } + + void (*kernel)(void) = NULL; + if(msg->kernel != NULL) { + kernel = dyld_lookup(msg->kernel, msg->library_info); + if(kernel == NULL) { + char error[256]; + snprintf(error, sizeof(error), + "kernel '%s' not found in library", msg->kernel); + struct msg_load_reply msg = { + .type = MESSAGE_TYPE_LOAD_REPLY, + .error = error + }; + mailbox_send(&msg); + while(1); + } + } + + mailbox_acknowledge(); + + if(kernel) { + void (*run_closure)(void *) = msg->library_info->init; + run_closure(kernel); + + struct msg_base msg; + msg.type = MESSAGE_TYPE_FINISHED; + mailbox_send_and_wait(&msg); + } + + while(1); +} + +/* called from __artiq_personality */ +void __artiq_terminate(struct artiq_exception *artiq_exn, + struct artiq_backtrace_item *backtrace, + size_t backtrace_size) { + struct msg_exception msg; + + msg.type = MESSAGE_TYPE_EXCEPTION; + msg.exception = artiq_exn; + msg.backtrace = backtrace; + msg.backtrace_size = backtrace_size; + mailbox_send(&msg); + while(1); } -long long int now_init(void); long long int now_init(void) { struct msg_base request; @@ -72,8 +259,10 @@ long long int now_init(void) mailbox_send_and_wait(&request); reply = mailbox_wait_and_receive(); - if(reply->type != MESSAGE_TYPE_NOW_INIT_REPLY) - exception_raise_params(EID_INTERNAL_ERROR, 1, 0, 0); + if(reply->type != MESSAGE_TYPE_NOW_INIT_REPLY) { + log("Malformed MESSAGE_TYPE_NOW_INIT_REQUEST reply type"); + while(1); + } now = reply->now; mailbox_acknowledge(); @@ -85,7 +274,6 @@ long long int now_init(void) return now; } -void now_save(long long int now); void now_save(long long int now) { struct msg_now_save request; @@ -106,8 +294,10 @@ int watchdog_set(int ms) mailbox_send_and_wait(&request); reply = mailbox_wait_and_receive(); - if(reply->type != MESSAGE_TYPE_WATCHDOG_SET_REPLY) - exception_raise_params(EID_INTERNAL_ERROR, 2, 0, 0); + if(reply->type != MESSAGE_TYPE_WATCHDOG_SET_REPLY) { + log("Malformed MESSAGE_TYPE_WATCHDOG_SET_REQUEST reply type"); + while(1); + } id = reply->id; mailbox_acknowledge(); @@ -127,7 +317,6 @@ int rpc(int rpc_num, ...) { struct msg_rpc_request request; struct msg_rpc_reply *reply; - int eid, retval; request.type = MESSAGE_TYPE_RPC_REQUEST; request.rpc_num = rpc_num; @@ -136,15 +325,21 @@ int rpc(int rpc_num, ...) va_end(request.args); reply = mailbox_wait_and_receive(); - if(reply->type != MESSAGE_TYPE_RPC_REPLY) - exception_raise_params(EID_INTERNAL_ERROR, 3, 0, 0); - eid = reply->eid; - retval = reply->retval; - mailbox_acknowledge(); + if(reply->type != MESSAGE_TYPE_RPC_REPLY) { + log("Malformed MESSAGE_TYPE_RPC_REPLY reply type"); + while(1); + } - if(eid != EID_NONE) - exception_raise(eid); - return retval; + if(reply->exception != NULL) { + struct artiq_exception exception; + memcpy(&exception, reply->exception, sizeof(exception)); + mailbox_acknowledge(); + __artiq_raise(&exception); + } else { + int retval = reply->retval; + mailbox_acknowledge(); + return retval; + } } void lognonl(const char *fmt, ...) diff --git a/soc/runtime/ksupport.h b/soc/runtime/ksupport.h new file mode 100644 index 000000000..b78f934f8 --- /dev/null +++ b/soc/runtime/ksupport.h @@ -0,0 +1,12 @@ +#ifndef __KSTARTUP_H +#define __KSTARTUP_H + +long long int now_init(void); +void now_save(long long int now); +int watchdog_set(int ms); +void watchdog_clear(int id); +int rpc(int rpc_num, ...); +void lognonl(const char *fmt, ...); +void log(const char *fmt, ...); + +#endif /* __KSTARTUP_H */ diff --git a/soc/runtime/ksupport.ld b/soc/runtime/ksupport.ld index 3cc585399..9f9ca4bb9 100644 --- a/soc/runtime/ksupport.ld +++ b/soc/runtime/ksupport.ld @@ -4,10 +4,10 @@ ENTRY(_start) INCLUDE generated/regions.ld /* First 4M of main memory are reserved for runtime code/data - * then comes kernel memory. First 32K of kernel memory are for support code. + * then comes kernel memory. First 128K of kernel memory are for support code. */ MEMORY { - ksupport : ORIGIN = 0x40400000, LENGTH = 0x8000 + ksupport (RWX) : ORIGIN = 0x40400000, LENGTH = 0x20000 } /* On AMP systems, kernel stack is at the end of main RAM, @@ -15,6 +15,13 @@ MEMORY { */ PROVIDE(_fstack = 0x40000000 + LENGTH(main_ram) - 1024*1024 - 4); +/* Force ld to make the ELF header as loadable. */ +PHDRS +{ + text PT_LOAD FILEHDR PHDRS; + eh_frame PT_GNU_EH_FRAME; +} + SECTIONS { .text : @@ -22,7 +29,7 @@ SECTIONS _ftext = .; *(.text .stub .text.* .gnu.linkonce.t.*) _etext = .; - } > ksupport + } :text .rodata : { @@ -33,6 +40,16 @@ SECTIONS _erodata = .; } > ksupport + .eh_frame : + { + *(.eh_frame) + } :text + + .eh_frame_hdr : + { + *(.eh_frame_hdr) + } :text :eh_frame + .data : { . = ALIGN(4); @@ -41,7 +58,7 @@ SECTIONS *(.data1) *(.sdata .sdata.* .gnu.linkonce.s.*) _edata = .; - } > ksupport + } .bss : { @@ -57,5 +74,5 @@ SECTIONS _ebss = .; . = ALIGN(8); _heapstart = .; - } > ksupport + } } diff --git a/soc/runtime/messages.h b/soc/runtime/messages.h index b0685105d..4b633a5d5 100644 --- a/soc/runtime/messages.h +++ b/soc/runtime/messages.h @@ -2,8 +2,10 @@ #define __MESSAGES_H #include +#include enum { + MESSAGE_TYPE_LOAD_REPLY, MESSAGE_TYPE_NOW_INIT_REQUEST, MESSAGE_TYPE_NOW_INIT_REPLY, MESSAGE_TYPE_NOW_SAVE, @@ -34,6 +36,17 @@ struct msg_base { /* kernel messages */ +struct msg_load_request { + void *library; + struct dyld_info *library_info; + const char *kernel; +}; + +struct msg_load_reply { + int type; + const char *error; +}; + struct msg_now_init_reply { int type; long long int now; @@ -46,8 +59,9 @@ struct msg_now_save { struct msg_exception { int type; - int eid; - long long int eparams[3]; + struct artiq_exception *exception; + struct artiq_backtrace_item *backtrace; + size_t backtrace_size; }; struct msg_watchdog_set_request { @@ -73,7 +87,7 @@ struct msg_rpc_request { struct msg_rpc_reply { int type; - int eid; + struct artiq_exception *exception; int retval; }; diff --git a/soc/runtime/rtio.h b/soc/runtime/rtio.h index be1591554..4545d302c 100644 --- a/soc/runtime/rtio.h +++ b/soc/runtime/rtio.h @@ -2,7 +2,7 @@ #define __RTIO_H #include -#include "exceptions.h" +#include "artiq_personality.h" #define RTIO_O_STATUS_FULL 1 #define RTIO_O_STATUS_UNDERFLOW 2 @@ -24,12 +24,14 @@ static inline void rtio_write_and_process_status(long long int timestamp, int ch while(rtio_o_status_read() & RTIO_O_STATUS_FULL); if(status & RTIO_O_STATUS_UNDERFLOW) { rtio_o_underflow_reset_write(1); - exception_raise_params(EID_RTIO_UNDERFLOW, + artiq_raise_from_c("RTIOUnderflow", + "RTIO underflow at {0}mu, channel {1}, counter {2}", timestamp, channel, rtio_get_counter()); } if(status & RTIO_O_STATUS_SEQUENCE_ERROR) { rtio_o_sequence_error_reset_write(1); - exception_raise_params(EID_RTIO_SEQUENCE_ERROR, + artiq_raise_from_c("RTIOSequenceError", + "RTIO sequence error at {0}mu, channel {1}", timestamp, channel, 0); } } diff --git a/soc/runtime/linker.ld b/soc/runtime/runtime.ld similarity index 71% rename from soc/runtime/linker.ld rename to soc/runtime/runtime.ld index 4a4217f1e..dacfe535d 100644 --- a/soc/runtime/linker.ld +++ b/soc/runtime/runtime.ld @@ -10,6 +10,13 @@ MEMORY { runtime : ORIGIN = 0x40000000, LENGTH = 0x400000 /* 4M */ } +/* First 4M of main memory are reserved for runtime code/data + * then comes kernel memory. First 32K of kernel memory are for support code. + */ +MEMORY { + kernel : ORIGIN = 0x40400000, LENGTH = 0x8000 +} + /* Kernel memory space start right after the runtime, * and ends before the runtime stack. * Runtime stack is always at the end of main_ram. @@ -17,6 +24,11 @@ MEMORY { */ PROVIDE(_fstack = 0x40000000 + LENGTH(main_ram) - 4); +/* On AMP systems, kernel stack is at the end of main RAM, + * before the runtime stack. Leave 1M for runtime stack. + */ +PROVIDE(_kernel_fstack = 0x40000000 + LENGTH(main_ram) - 1024*1024 - 4); + SECTIONS { .text : @@ -58,6 +70,12 @@ SECTIONS . = ALIGN(4); _ebss = .; . = ALIGN(8); - _heapstart = .; } > runtime + + /DISCARD/ : + { + *(.eh_frame) + } + + _heapstart = .; } diff --git a/soc/runtime/services.c b/soc/runtime/services.c deleted file mode 100644 index 74bdeef71..000000000 --- a/soc/runtime/services.c +++ /dev/null @@ -1,78 +0,0 @@ -#include - -#include "elf_loader.h" -#include "session.h" -#include "clock.h" -#include "ttl.h" -#include "dds.h" -#include "exceptions.h" -#include "services.h" - -#include "service_table.h" - -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wimplicit-int" -extern __divsi3, __modsi3, __ledf2, __gedf2, __unorddf2, __eqdf2, __ltdf2, - __nedf2, __gtdf2, __negsf2, __negdf2, __addsf3, __subsf3, __mulsf3, - __divsf3, __lshrdi3, __muldi3, __divdi3, __ashldi3, __ashrdi3, - __udivmoddi4, __floatsisf, __floatunsisf, __fixsfsi, __fixunssfsi, - __adddf3, __subdf3, __muldf3, __divdf3, __floatsidf, __floatunsidf, - __floatdidf, __fixdfsi, __fixdfdi, __fixunsdfsi, __clzsi2, __ctzsi2, - __udivdi3, __umoddi3, __moddi3; -#pragma GCC diagnostic pop - -static const struct symbol compiler_rt[] = { - {"divsi3", &__divsi3}, - {"modsi3", &__modsi3}, - {"ledf2", &__ledf2}, - {"gedf2", &__gedf2}, - {"unorddf2", &__unorddf2}, - {"eqdf2", &__eqdf2}, - {"ltdf2", &__ltdf2}, - {"nedf2", &__nedf2}, - {"gtdf2", &__gtdf2}, - {"negsf2", &__negsf2}, - {"negdf2", &__negdf2}, - {"addsf3", &__addsf3}, - {"subsf3", &__subsf3}, - {"mulsf3", &__mulsf3}, - {"divsf3", &__divsf3}, - {"lshrdi3", &__lshrdi3}, - {"muldi3", &__muldi3}, - {"divdi3", &__divdi3}, - {"ashldi3", &__ashldi3}, - {"ashrdi3", &__ashrdi3}, - {"udivmoddi4", &__udivmoddi4}, - {"floatsisf", &__floatsisf}, - {"floatunsisf", &__floatunsisf}, - {"fixsfsi", &__fixsfsi}, - {"fixunssfsi", &__fixunssfsi}, - {"adddf3", &__adddf3}, - {"subdf3", &__subdf3}, - {"muldf3", &__muldf3}, - {"divdf3", &__divdf3}, - {"floatsidf", &__floatsidf}, - {"floatunsidf", &__floatunsidf}, - {"floatdidf", &__floatdidf}, - {"fixdfsi", &__fixdfsi}, - {"fixdfdi", &__fixdfdi}, - {"fixunsdfsi", &__fixunsdfsi}, - {"clzsi2", &__clzsi2}, - {"ctzsi2", &__ctzsi2}, - {"udivdi3", &__udivdi3}, - {"umoddi3", &__umoddi3}, - {"moddi3", &__moddi3}, - {NULL, NULL} -}; - -void *resolve_service_symbol(const char *name) -{ - if(strncmp(name, "__", 2) != 0) - return NULL; - name += 2; - if(strncmp(name, "syscall_", 8) == 0) - return find_symbol(syscalls, name + 8); - if(strncmp(name, "eh_", 3) == 0) - return find_symbol(eh, name + 3); - return find_symbol(compiler_rt, name); -} diff --git a/soc/runtime/services.h b/soc/runtime/services.h deleted file mode 100644 index 9c9dcf630..000000000 --- a/soc/runtime/services.h +++ /dev/null @@ -1,6 +0,0 @@ -#ifndef __SERVICES_H -#define __SERVICES_H - -void *resolve_service_symbol(const char *name); - -#endif /* __SERVICES_H */ diff --git a/soc/runtime/session.c b/soc/runtime/session.c index c0908639c..511cd8353 100644 --- a/soc/runtime/session.c +++ b/soc/runtime/session.c @@ -10,7 +10,7 @@ #include "clock.h" #include "log.h" #include "kloader.h" -#include "exceptions.h" +#include "artiq_personality.h" #include "flash_storage.h" #include "rtiocrg.h" #include "session.h" @@ -19,7 +19,7 @@ #define BUFFER_OUT_SIZE (1024*1024) static int buffer_in_index; -/* The 9th byte (right after the header) of buffer_in must be aligned +/* The 9th byte (right after the header) of buffer_in must be aligned * to a 32-bit boundary for elf_loader to work. */ static struct { @@ -85,8 +85,8 @@ enum { REMOTEMSG_TYPE_LOG_REQUEST = 1, REMOTEMSG_TYPE_IDENT_REQUEST, REMOTEMSG_TYPE_SWITCH_CLOCK, - - REMOTEMSG_TYPE_LOAD_OBJECT, + + REMOTEMSG_TYPE_LOAD_LIBRARY, REMOTEMSG_TYPE_RUN_KERNEL, REMOTEMSG_TYPE_RPC_REPLY, @@ -161,23 +161,22 @@ static int process_input(void) buffer_out[8] = REMOTEMSG_TYPE_CLOCK_SWITCH_FAILED; submit_output(9); break; - case REMOTEMSG_TYPE_LOAD_OBJECT: + case REMOTEMSG_TYPE_LOAD_LIBRARY: if(user_kernel_state >= USER_KERNEL_RUNNING) { - log("Attempted to load new kernel while already running"); + log("Attempted to load new kernel library while already running"); buffer_out[8] = REMOTEMSG_TYPE_LOAD_FAILED; submit_output(9); - break; + break; } - if(kloader_load(&buffer_in[9], get_in_packet_len() - 8)) { + if(kloader_load_library(&buffer_in[9])) { buffer_out[8] = REMOTEMSG_TYPE_LOAD_COMPLETED; user_kernel_state = USER_KERNEL_LOADED; - } else + } else { buffer_out[8] = REMOTEMSG_TYPE_LOAD_FAILED; + } submit_output(9); break; case REMOTEMSG_TYPE_RUN_KERNEL: { - kernel_function k; - if(user_kernel_state != USER_KERNEL_LOADED) { log("Attempted to run kernel while not in the LOADED state"); buffer_out[8] = REMOTEMSG_TYPE_KERNEL_STARTUP_FAILED; @@ -193,16 +192,14 @@ static int process_input(void) } buffer_in[buffer_in_index] = 0; - k = kloader_find((char *)&buffer_in[9]); - if(k == NULL) { + watchdog_init(); + if(!kloader_start_kernel((char *)&buffer_in[9])) { log("Failed to find kernel entry point '%s' in object", &buffer_in[9]); buffer_out[8] = REMOTEMSG_TYPE_KERNEL_STARTUP_FAILED; submit_output(9); break; } - watchdog_init(); - kloader_start_user_kernel(k); user_kernel_state = USER_KERNEL_RUNNING; break; } @@ -215,7 +212,7 @@ static int process_input(void) } reply.type = MESSAGE_TYPE_RPC_REPLY; - memcpy(&reply.eid, &buffer_in[9], 4); + // FIXME memcpy(&reply.eid, &buffer_in[9], 4); memcpy(&reply.retval, &buffer_in[13], 4); mailbox_send_and_wait(&reply); user_kernel_state = USER_KERNEL_RUNNING; @@ -481,8 +478,8 @@ static int process_kmsg(struct msg_base *umsg) struct msg_exception *msg = (struct msg_exception *)umsg; buffer_out[8] = REMOTEMSG_TYPE_KERNEL_EXCEPTION; - memcpy(&buffer_out[9], &msg->eid, 4); - memcpy(&buffer_out[13], msg->eparams, 3*8); + // memcpy(&buffer_out[9], &msg->eid, 4); + // memcpy(&buffer_out[13], msg->eparams, 3*8); submit_output(9+4+3*8); kloader_stop(); @@ -545,7 +542,7 @@ void session_poll(void **data, int *len) l = get_out_packet_len(); - /* If the output buffer is available, + /* If the output buffer is available, * check if the kernel CPU has something to transmit. */ if(l == 0) { diff --git a/soc/runtime/ttl.c b/soc/runtime/ttl.c index 387b977b1..577ab1eeb 100644 --- a/soc/runtime/ttl.c +++ b/soc/runtime/ttl.c @@ -1,6 +1,6 @@ #include -#include "exceptions.h" +#include "artiq_personality.h" #include "rtio.h" #include "ttl.h" @@ -40,7 +40,8 @@ long long int ttl_get(int channel, long long int time_limit) while((status = rtio_i_status_read())) { if(rtio_i_status_read() & RTIO_I_STATUS_OVERFLOW) { rtio_i_overflow_reset_write(1); - exception_raise_params(EID_RTIO_OVERFLOW, + artiq_raise_from_c("RTIOOverflow", + "RTIO overflow at channel {0}", channel, 0, 0); } if(rtio_get_counter() >= time_limit) { From 8d0222c297ecbc8efd707f2a03200809018c4bfc Mon Sep 17 00:00:00 2001 From: whitequark Date: Sun, 2 Aug 2015 16:35:49 +0300 Subject: [PATCH 172/369] =?UTF-8?q?Rename=20artiq=5Fcoreconfig=20=E2=86=92?= =?UTF-8?q?=20artiq=5Fcoretool;=20add=20log=20subcommand.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...{artiq_coreconfig.py => artiq_coretool.py} | 46 +++++++++++++------ doc/manual/core_device_flash_storage.rst | 2 +- doc/manual/installing.rst | 4 +- doc/manual/utilities.rst | 42 +++++++++-------- setup.py | 2 +- 5 files changed, 57 insertions(+), 39 deletions(-) rename artiq/frontend/{artiq_coreconfig.py => artiq_coretool.py} (75%) diff --git a/artiq/frontend/artiq_coreconfig.py b/artiq/frontend/artiq_coretool.py similarity index 75% rename from artiq/frontend/artiq_coreconfig.py rename to artiq/frontend/artiq_coretool.py index 301657117..41649f03e 100755 --- a/artiq/frontend/artiq_coreconfig.py +++ b/artiq/frontend/artiq_coretool.py @@ -11,15 +11,26 @@ def to_bytes(string): def get_argparser(): - parser = argparse.ArgumentParser(description="ARTIQ core device config " - "remote access") + parser = argparse.ArgumentParser(description="ARTIQ core device " + "remote access tool") + parser.add_argument("--ddb", default="ddb.pyon", + help="device database file") + subparsers = parser.add_subparsers(dest="action") subparsers.required = True - p_read = subparsers.add_parser("read", + + # Log Read command + subparsers.add_parser("log", + help="read from the core device log ring buffer") + + # Configuration Read command + p_read = subparsers.add_parser("cfg-read", help="read key from core device config") p_read.add_argument("key", type=to_bytes, help="key to be read from core device config") - p_write = subparsers.add_parser("write", + + # Configuration Write command + p_write = subparsers.add_parser("cfg-write", help="write key-value records to core " "device config") p_write.add_argument("-s", "--string", nargs=2, action="append", @@ -31,14 +42,17 @@ def get_argparser(): metavar=("KEY", "FILENAME"), help="key and file whose content to be written to " "core device config") - subparsers.add_parser("erase", help="erase core device config") - p_delete = subparsers.add_parser("delete", + + # Configuration Delete command + p_delete = subparsers.add_parser("cfg-delete", help="delete key from core device config") p_delete.add_argument("key", nargs=argparse.REMAINDER, default=[], type=to_bytes, help="key to be deleted from core device config") - parser.add_argument("--ddb", default="ddb.pyon", - help="device database file") + + # Configuration Erase command + subparsers.add_parser("cfg-erase", help="erase core device config") + return parser @@ -48,23 +62,25 @@ def main(): try: comm = dmgr.get("comm") - if args.action == "read": + if args.action == "log": + print(comm.get_log()) + elif args.action == "cfg-read": value = comm.flash_storage_read(args.key) if not value: print("Key {} does not exist".format(args.key)) else: print(value) - elif args.action == "erase": - comm.flash_storage_erase() - elif args.action == "delete": - for key in args.key: - comm.flash_storage_remove(key) - elif args.action == "write": + elif args.action == "cfg-write": for key, value in args.string: comm.flash_storage_write(key, value) for key, filename in args.file: with open(filename, "rb") as fi: comm.flash_storage_write(key, fi.read()) + elif args.action == "cfg-delete": + for key in args.key: + comm.flash_storage_remove(key) + elif args.action == "cfg-erase": + comm.flash_storage_erase() finally: dmgr.close_devices() diff --git a/doc/manual/core_device_flash_storage.rst b/doc/manual/core_device_flash_storage.rst index fb2bb8d9a..cc5fe1b2d 100644 --- a/doc/manual/core_device_flash_storage.rst +++ b/doc/manual/core_device_flash_storage.rst @@ -11,4 +11,4 @@ This storage area is used to store the core device MAC address, IP address and e The flash storage area is one sector (64 kB) large and is organized as a list of key-value records. -This flash storage space can be accessed by using the artiq_coreconfig.py :ref:`core-device-configuration-tool`. +This flash storage space can be accessed by using the artiq_coretool.py :ref:`core-device-access-tool`. diff --git a/doc/manual/installing.rst b/doc/manual/installing.rst index 8de011a29..79dbac59f 100644 --- a/doc/manual/installing.rst +++ b/doc/manual/installing.rst @@ -289,9 +289,9 @@ To flash the ``idle`` kernel: * Write it into the core device configuration flash storage: :: - $ artiq_coreconfig write -f idle_kernel idle.elf + $ artiq_coretool cfg-write -f idle_kernel idle.elf -.. note:: You can find more information about how to use the ``artiq_coreconfig`` tool on the :ref:`Utilities ` page. +.. note:: You can find more information about how to use the ``artiq_coretool`` utility on the :ref:`Utilities ` page. Installing the host-side software ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/doc/manual/utilities.rst b/doc/manual/utilities.rst index a507291ae..23df5b581 100644 --- a/doc/manual/utilities.rst +++ b/doc/manual/utilities.rst @@ -93,60 +93,62 @@ This tool compiles key/value pairs into a binary image suitable for flashing int :ref: artiq.frontend.artiq_mkfs.get_argparser :prog: artiq_mkfs -.. _core-device-configuration-tool: +.. _core-device-access-tool: -Core device configuration tool ------------------------------- +Core device access tool +----------------------- -The artiq_coreconfig tool allows to read, write and remove key-value records from the :ref:`core-device-flash-storage`. +The artiq_coretool utility allows to perform maintenance on the core device: -It also allows to erase the entire flash storage area. + * read core device logs; + * as well as read, write and remove key-value records from the :ref:`core-device-flash-storage`; + * erase the entire flash storage area. To use this tool, you need to specify a ``ddb.pyon`` DDB file which contains a ``comm`` device (an example is provided in ``artiq/examples/master/ddb.pyon``). This tells the tool how to connect to the core device (via serial or via TCP) and with which parameters (baudrate, serial device, IP address, TCP port). -When not specified, the artiq_coreconfig tool will assume that there is a file named ``ddb.pyon`` in the current directory. +When not specified, the artiq_coretool utility will assume that there is a file named ``ddb.pyon`` in the current directory. To read the record whose key is ``mac``:: - $ artiq_coreconfig read mac + $ artiq_coretool cfg-read mac To write the value ``test_value`` in the key ``my_key``:: - $ artiq_coreconfig write -s my_key test_value - $ artiq_coreconfig read my_key + $ artiq_coretool cfg-write -s my_key test_value + $ artiq_coretool cfg-read my_key b'test_value' You can also write entire files in a record using the ``-f`` parameter. This is useful for instance to write the ``idle`` kernel in the flash storage:: - $ artiq_coreconfig write -f idle_kernel idle.elf - $ artiq_coreconfig read idle_kernel | head -c9 + $ artiq_coretool cfg-write -f idle_kernel idle.elf + $ artiq_coretool cfg-read idle_kernel | head -c9 b'\x7fELF You can write several records at once:: - $ artiq_coreconfig write -s key1 value1 -f key2 filename -s key3 value3 + $ artiq_coretool cfg-write -s key1 value1 -f key2 filename -s key3 value3 To remove the previously written key ``my_key``:: - $ artiq_coreconfig delete my_key + $ artiq_coretool cfg-delete my_key You can remove several keys at once:: - $ artiq_coreconfig delete key1 key2 + $ artiq_coretool cfg-delete key1 key2 To erase the entire flash storage area:: - $ artiq_coreconfig erase + $ artiq_coretool cfg-erase You don't need to remove a record in order to change its value, just overwrite it:: - $ artiq_coreconfig write -s my_key some_value - $ artiq_coreconfig write -s my_key some_other_value - $ artiq_coreconfig read my_key + $ artiq_coretool cfg-write -s my_key some_value + $ artiq_coretool cfg-write -s my_key some_other_value + $ artiq_coretool cfg-read my_key b'some_other_value' .. argparse:: - :ref: artiq.frontend.artiq_coreconfig.get_argparser - :prog: artiq_coreconfig + :ref: artiq.frontend.artiq_coretool.get_argparser + :prog: artiq_coretool diff --git a/setup.py b/setup.py index 2d6870f70..2d7d8bfa5 100755 --- a/setup.py +++ b/setup.py @@ -27,7 +27,7 @@ requirements = [ scripts = [ "artiq_client=artiq.frontend.artiq_client:main", "artiq_compile=artiq.frontend.artiq_compile:main", - "artiq_coreconfig=artiq.frontend.artiq_coreconfig:main", + "artiq_coretool=artiq.frontend.artiq_coretool:main", "artiq_ctlmgr=artiq.frontend.artiq_ctlmgr:main", "artiq_gui=artiq.frontend.artiq_gui:main", "artiq_master=artiq.frontend.artiq_master:main", From 722dfef97b15a0ef2d82b8e3bfb0848c908d7045 Mon Sep 17 00:00:00 2001 From: whitequark Date: Thu, 6 Aug 2015 07:59:15 +0300 Subject: [PATCH 173/369] artiq_personality: simplify. --- soc/runtime/artiq_personality.c | 6 ------ 1 file changed, 6 deletions(-) diff --git a/soc/runtime/artiq_personality.c b/soc/runtime/artiq_personality.c index 2dc2350a5..e35fdcf13 100644 --- a/soc/runtime/artiq_personality.c +++ b/soc/runtime/artiq_personality.c @@ -8,14 +8,8 @@ /* Logging */ #ifndef NDEBUG -#if defined(__or1k__) -#include "log.h" -#define EH_LOG0(fmt) log("%s: " fmt, __func__) -#define EH_LOG(fmt, ...) log("%s: " fmt, __func__, __VA_ARGS__) -#else #define EH_LOG0(fmt) fprintf(stderr, "%s: " fmt "\n", __func__) #define EH_LOG(fmt, ...) fprintf(stderr, "%s: " fmt "\n", __func__, __VA_ARGS__) -#endif #else #define EH_LOG0(fmt) #define EH_LOG(fmt, ...) From 98cd4288c158284c0b48e2d006cb9dbf54c292b2 Mon Sep 17 00:00:00 2001 From: whitequark Date: Thu, 6 Aug 2015 08:25:13 +0300 Subject: [PATCH 174/369] artiq_personality: cast exception params so that %lld is always valid. --- soc/runtime/artiq_personality.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/soc/runtime/artiq_personality.c b/soc/runtime/artiq_personality.c index e35fdcf13..d5ba26b24 100644 --- a/soc/runtime/artiq_personality.c +++ b/soc/runtime/artiq_personality.c @@ -242,7 +242,9 @@ static struct artiq_raised_exception inflight; void __artiq_raise(struct artiq_exception *artiq_exn) { EH_LOG("===> raise (name=%s, msg=%s, params=[%lld,%lld,%lld])", artiq_exn->name, artiq_exn->message, - artiq_exn->param[0], artiq_exn->param[1], artiq_exn->param[2]); + (long long int)artiq_exn->param[0], + (long long int)artiq_exn->param[1], + (long long int)artiq_exn->param[2]); memmove(&inflight.artiq, artiq_exn, sizeof(struct artiq_exception)); inflight.unwind.exception_class = ARTIQ_EXCEPTION_CLASS; From ca52b2fdd05dfcb4b0fd8e2a2d756c9fad68a1d6 Mon Sep 17 00:00:00 2001 From: whitequark Date: Thu, 6 Aug 2015 08:24:06 +0300 Subject: [PATCH 175/369] compiler.transforms.ARTIQIRGenerator: fix typo. --- artiq/compiler/transforms/artiq_ir_generator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/artiq/compiler/transforms/artiq_ir_generator.py b/artiq/compiler/transforms/artiq_ir_generator.py index 3872e014e..845ef3189 100644 --- a/artiq/compiler/transforms/artiq_ir_generator.py +++ b/artiq/compiler/transforms/artiq_ir_generator.py @@ -238,7 +238,7 @@ class ARTIQIRGenerator(algorithm.Visitor): return self.append(ir.Closure(func, self.current_env)) def visit_FunctionDefT(self, node): - func = self.visit_function(node, is_lambda=False, is_internal=len(name) > 2) + func = self.visit_function(node, is_lambda=False, is_internal=len(self.name) > 2) self._set_local(node.name, func) def visit_Return(self, node): From 7562d397500ad7e92355c05d803c31f51938ce34 Mon Sep 17 00:00:00 2001 From: whitequark Date: Thu, 6 Aug 2015 08:24:41 +0300 Subject: [PATCH 176/369] compiler.module: split off inferencing from Module.__init__. --- artiq/compiler/__init__.py | 2 +- artiq/compiler/module.py | 65 ++++++++++++------- artiq/compiler/testbench/inferencer.py | 2 +- artiq/compiler/testbench/irgen.py | 4 +- artiq/compiler/testbench/jit.py | 4 +- artiq/compiler/testbench/llvmgen.py | 4 +- artiq/compiler/testbench/perf.py | 4 +- artiq/compiler/testbench/shlib.py | 4 +- artiq/compiler/testbench/signature.py | 4 +- .../compiler/transforms/asttyped_rewriter.py | 6 +- 10 files changed, 58 insertions(+), 41 deletions(-) diff --git a/artiq/compiler/__init__.py b/artiq/compiler/__init__.py index 8f210ac92..1d187a71b 100644 --- a/artiq/compiler/__init__.py +++ b/artiq/compiler/__init__.py @@ -1 +1 @@ -from .module import Module +from .module import Module, Source diff --git a/artiq/compiler/module.py b/artiq/compiler/module.py index 133284e95..2f62da1d8 100644 --- a/artiq/compiler/module.py +++ b/artiq/compiler/module.py @@ -1,36 +1,62 @@ """ -The :class:`Module` class encapsulates a single Python +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, validators -class Module: +class Source: def __init__(self, source_buffer, engine=None): if engine is None: - engine = diagnostic.Engine(all_errors_are_fatal=True) + self.engine = diagnostic.Engine(all_errors_are_fatal=True) + else: + self.engine = engine self.name, _ = os.path.splitext(os.path.basename(source_buffer.name)) - asttyped_rewriter = transforms.ASTTypedRewriter(engine=engine) + asttyped_rewriter = transforms.ASTTypedRewriter(engine=engine, + globals=prelude.globals()) inferencer = transforms.Inferencer(engine=engine) - int_monomorphizer = transforms.IntMonomorphizer(engine=engine) - monomorphism_validator = validators.MonomorphismValidator(engine=engine) - escape_validator = validators.EscapeValidator(engine=engine) - artiq_ir_generator = transforms.ARTIQIRGenerator(engine=engine, module_name=self.name) - dead_code_eliminator = transforms.DeadCodeEliminator(engine=engine) - local_access_validator = validators.LocalAccessValidator(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) - int_monomorphizer.visit(self.typedtree) - inferencer.visit(self.typedtree) - monomorphism_validator.visit(self.typedtree) - escape_validator.visit(self.typedtree) - self.artiq_ir = artiq_ir_generator.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): + int_monomorphizer = transforms.IntMonomorphizer(engine=src.engine) + inferencer = transforms.Inferencer(engine=src.engine) + monomorphism_validator = validators.MonomorphismValidator(engine=src.engine) + escape_validator = validators.EscapeValidator(engine=src.engine) + artiq_ir_generator = transforms.ARTIQIRGenerator(engine=src.engine, + module_name=src.name) + dead_code_eliminator = transforms.DeadCodeEliminator(engine=src.engine) + local_access_validator = validators.LocalAccessValidator(engine=src.engine) + + self.name = src.name + self.globals = src.globals + int_monomorphizer.visit(src.typedtree) + inferencer.visit(src.typedtree) + monomorphism_validator.visit(src.typedtree) + escape_validator.visit(src.typedtree) + self.artiq_ir = artiq_ir_generator.visit(src.typedtree) dead_code_eliminator.process(self.artiq_ir) # local_access_validator.process(self.artiq_ir) @@ -43,15 +69,6 @@ class Module: """Return the name of the function that is the entry point of this module.""" return self.name + ".__modinit__" - @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) - def __repr__(self): printer = types.TypePrinter() globals = ["%s: %s" % (var, printer.name(self.globals[var])) for var in self.globals] diff --git a/artiq/compiler/testbench/inferencer.py b/artiq/compiler/testbench/inferencer.py index e6e73ea60..d11150c43 100644 --- a/artiq/compiler/testbench/inferencer.py +++ b/artiq/compiler/testbench/inferencer.py @@ -66,7 +66,7 @@ def main(): buf = source.Buffer("".join(fileinput.input()).expandtabs(), os.path.basename(fileinput.filename())) parsed, comments = parse_buffer(buf, engine=engine) - typed = ASTTypedRewriter(engine=engine).visit(parsed) + typed = ASTTypedRewriter(engine=engine, globals=prelude.globals()).visit(parsed) Inferencer(engine=engine).visit(typed) if monomorphize: IntMonomorphizer(engine=engine).visit(typed) diff --git a/artiq/compiler/testbench/irgen.py b/artiq/compiler/testbench/irgen.py index 20e9334ca..4add27c20 100644 --- a/artiq/compiler/testbench/irgen.py +++ b/artiq/compiler/testbench/irgen.py @@ -1,6 +1,6 @@ import sys, fileinput from pythonparser import diagnostic -from .. import Module +from .. import Module, Source def main(): def process_diagnostic(diag): @@ -11,7 +11,7 @@ def main(): engine = diagnostic.Engine() engine.process = process_diagnostic - mod = Module.from_string("".join(fileinput.input()).expandtabs(), engine=engine) + mod = Module(Source.from_string("".join(fileinput.input()).expandtabs(), engine=engine)) for fn in mod.artiq_ir: print(fn) diff --git a/artiq/compiler/testbench/jit.py b/artiq/compiler/testbench/jit.py index 4e99504c1..717bdc555 100644 --- a/artiq/compiler/testbench/jit.py +++ b/artiq/compiler/testbench/jit.py @@ -1,7 +1,7 @@ import os, sys, fileinput, ctypes from pythonparser import diagnostic from llvmlite_artiq import binding as llvm -from .. import Module +from .. import Module, Source from ..targets import NativeTarget def main(): @@ -19,7 +19,7 @@ def main(): source = "".join(fileinput.input()) source = source.replace("#ARTIQ#", "") - mod = Module.from_string(source.expandtabs(), engine=engine) + mod = Module(Source.from_string(source.expandtabs(), engine=engine)) target = NativeTarget() llmod = mod.build_llvm_ir(target) diff --git a/artiq/compiler/testbench/llvmgen.py b/artiq/compiler/testbench/llvmgen.py index 3f72569ac..33500ec8b 100644 --- a/artiq/compiler/testbench/llvmgen.py +++ b/artiq/compiler/testbench/llvmgen.py @@ -1,7 +1,7 @@ import sys, fileinput from pythonparser import diagnostic from llvmlite_artiq import ir as ll -from .. import Module +from .. import Module, Source from ..targets import NativeTarget def main(): @@ -13,7 +13,7 @@ def main(): engine = diagnostic.Engine() engine.process = process_diagnostic - mod = Module.from_string("".join(fileinput.input()).expandtabs(), engine=engine) + mod = Module(Source.from_string("".join(fileinput.input()).expandtabs(), engine=engine)) target = NativeTarget() llmod = mod.build_llvm_ir(target=target) diff --git a/artiq/compiler/testbench/perf.py b/artiq/compiler/testbench/perf.py index c24946fb6..a5b3bdcc0 100644 --- a/artiq/compiler/testbench/perf.py +++ b/artiq/compiler/testbench/perf.py @@ -1,6 +1,6 @@ import sys, os, time, cProfile as profile, pstats from pythonparser import diagnostic -from .. import Module +from .. import Module, Source from ..targets import OR1KTarget def main(): @@ -17,7 +17,7 @@ def main(): engine.process = process_diagnostic # Make sure everything's valid - modules = [Module.from_filename(filename, engine=engine) + modules = [Module(Source.from_filename(filename, engine=engine)) for filename in sys.argv[1:]] def benchmark(f, name): diff --git a/artiq/compiler/testbench/shlib.py b/artiq/compiler/testbench/shlib.py index c3236129a..97c19f11b 100644 --- a/artiq/compiler/testbench/shlib.py +++ b/artiq/compiler/testbench/shlib.py @@ -1,6 +1,6 @@ import sys, os from pythonparser import diagnostic -from .. import Module +from .. import Module, Source from ..targets import OR1KTarget def main(): @@ -18,7 +18,7 @@ def main(): modules = [] for filename in sys.argv[1:]: - modules.append(Module.from_filename(filename, engine=engine)) + modules.append(Module(Source.from_filename(filename, engine=engine))) llobj = OR1KTarget().compile_and_link(modules) diff --git a/artiq/compiler/testbench/signature.py b/artiq/compiler/testbench/signature.py index 1882260bf..774259540 100644 --- a/artiq/compiler/testbench/signature.py +++ b/artiq/compiler/testbench/signature.py @@ -1,6 +1,6 @@ import sys, fileinput from pythonparser import diagnostic -from .. import Module +from .. import Module, Source def main(): if len(sys.argv) > 1 and sys.argv[1] == "+diag": @@ -21,7 +21,7 @@ def main(): engine.process = process_diagnostic try: - mod = Module.from_string("".join(fileinput.input()).expandtabs(), engine=engine) + mod = Module(Source.from_string("".join(fileinput.input()).expandtabs(), engine=engine)) print(repr(mod)) except: if not diag: raise diff --git a/artiq/compiler/transforms/asttyped_rewriter.py b/artiq/compiler/transforms/asttyped_rewriter.py index 307033d1d..cc0de6b2c 100644 --- a/artiq/compiler/transforms/asttyped_rewriter.py +++ b/artiq/compiler/transforms/asttyped_rewriter.py @@ -4,7 +4,7 @@ to a typedtree (:mod:`..asttyped`). """ from pythonparser import algorithm, diagnostic -from .. import asttyped, types, builtins, prelude +from .. import asttyped, types, builtins # This visitor will be called for every node with a scope, # i.e.: class, function, comprehension, lambda @@ -185,10 +185,10 @@ class ASTTypedRewriter(algorithm.Transformer): via :class:`LocalExtractor`. """ - def __init__(self, engine): + def __init__(self, engine, globals): self.engine = engine self.globals = None - self.env_stack = [prelude.globals()] + self.env_stack = [globals] def _find_name(self, name, loc): for typing_env in reversed(self.env_stack): From 1a969aa9e418ddb3176c0fcc404754dbb49aeeb3 Mon Sep 17 00:00:00 2001 From: whitequark Date: Fri, 7 Aug 2015 07:54:35 +0300 Subject: [PATCH 177/369] compiler.transforms.inferencer: accept and ignore @kernel decorator. --- artiq/compiler/builtins.py | 3 +++ artiq/compiler/prelude.py | 1 + artiq/compiler/transforms/inferencer.py | 7 +++++-- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/artiq/compiler/builtins.py b/artiq/compiler/builtins.py index 903900540..6b9feb9e2 100644 --- a/artiq/compiler/builtins.py +++ b/artiq/compiler/builtins.py @@ -146,6 +146,9 @@ def fn_round(): def fn_print(): return types.TBuiltinFunction("print") +def fn_kernel(): + return types.TBuiltinFunction("kernel") + def fn_syscall(): return types.TBuiltinFunction("syscall") diff --git a/artiq/compiler/prelude.py b/artiq/compiler/prelude.py index 600b7a800..3c58674c6 100644 --- a/artiq/compiler/prelude.py +++ b/artiq/compiler/prelude.py @@ -19,5 +19,6 @@ def globals(): "len": builtins.fn_len(), "round": builtins.fn_round(), "print": builtins.fn_print(), + "kernel": builtins.fn_kernel(), "syscall": builtins.fn_syscall(), } diff --git a/artiq/compiler/transforms/inferencer.py b/artiq/compiler/transforms/inferencer.py index 57aa9247d..e0338dcdc 100644 --- a/artiq/compiler/transforms/inferencer.py +++ b/artiq/compiler/transforms/inferencer.py @@ -887,10 +887,13 @@ class Inferencer(algorithm.Visitor): arg.loc, default.loc) def visit_FunctionDefT(self, node): - if any(node.decorator_list): + for index, decorator in enumerate(node.decorator_list): + if types.is_builtin(decorator.type, "kernel"): + continue + diag = diagnostic.Diagnostic("error", "decorators are not supported", {}, - node.at_locs[0], [node.decorator_list[0].loc]) + node.at_locs[index], [decorator.loc]) self.engine.process(diag) return From b5cf1e395d0af8c89317568727581fe2f9e7d2b3 Mon Sep 17 00:00:00 2001 From: whitequark Date: Fri, 7 Aug 2015 08:49:57 +0300 Subject: [PATCH 178/369] runtime: avoid race condition when running kernel. Also, don't bother passing kernel name: entry point is already recorded in DT_INIT when the kernel is linked. --- artiq/coredevice/comm_generic.py | 7 ++--- soc/runtime/kloader.c | 23 +++++++++----- soc/runtime/kloader.h | 2 +- soc/runtime/ksupport.c | 54 ++++++++++++-------------------- soc/runtime/messages.h | 2 +- soc/runtime/session.c | 15 +-------- 6 files changed, 41 insertions(+), 62 deletions(-) diff --git a/artiq/coredevice/comm_generic.py b/artiq/coredevice/comm_generic.py index 2daa24cdc..b538c33f7 100644 --- a/artiq/coredevice/comm_generic.py +++ b/artiq/coredevice/comm_generic.py @@ -130,10 +130,9 @@ class CommGeneric: if ty != _D2HMsgType.LOAD_COMPLETED: raise IOError("Incorrect reply from device: "+str(ty)) - def run(self, kname): - self._write_header(len(kname) + 9, _H2DMsgType.RUN_KERNEL) - self.write(bytes(kname, "ascii")) - logger.debug("running kernel: %s", kname) + def run(self): + self._write_header(9, _H2DMsgType.RUN_KERNEL) + logger.debug("running kernel") def flash_storage_read(self, key): self._write_header(9+len(key), _H2DMsgType.FLASH_READ_REQUEST) diff --git a/soc/runtime/kloader.c b/soc/runtime/kloader.c index ea8e4f5df..547907728 100644 --- a/soc/runtime/kloader.c +++ b/soc/runtime/kloader.c @@ -30,22 +30,29 @@ void kloader_start_bridge() start_kernel_cpu(NULL); } -static int load_or_start_kernel(void *library, const char *kernel) +static int load_or_start_kernel(void *library, int run_kernel) { static struct dyld_info library_info; struct msg_load_request request = { .library = library, .library_info = &library_info, - .kernel = kernel, + .run_kernel = run_kernel, }; start_kernel_cpu(&request); struct msg_load_reply *reply = mailbox_wait_and_receive(); - if(reply != NULL && reply->type == MESSAGE_TYPE_LOAD_REPLY) { - log("cannot load/run kernel: %s", reply->error); + if(reply->type != MESSAGE_TYPE_LOAD_REPLY) { + log("BUG: unexpected reply to load/run request"); return 0; } + if(reply->error != NULL) { + log("cannot load kernel: %s", reply->error); + return 0; + } + + mailbox_acknowledge(); + return 1; } @@ -56,12 +63,12 @@ int kloader_load_library(void *library) return 0; } - return load_or_start_kernel(library, NULL); + return load_or_start_kernel(library, 0); } -int kloader_start_kernel(const char *name) +void kloader_start_kernel() { - return load_or_start_kernel(NULL, name); + load_or_start_kernel(NULL, 1); } int kloader_start_idle_kernel(void) @@ -74,7 +81,7 @@ int kloader_start_idle_kernel(void) if(length <= 0) return 0; - return load_or_start_kernel(buffer, "test.__modinit__"); + return load_or_start_kernel(buffer, 1); #else return 0; #endif diff --git a/soc/runtime/kloader.h b/soc/runtime/kloader.h index 08036456c..834fd8d3d 100644 --- a/soc/runtime/kloader.h +++ b/soc/runtime/kloader.h @@ -12,7 +12,7 @@ int kloader_load_library(void *code); void kloader_start_bridge(void); int kloader_start_idle_kernel(void); -int kloader_start_kernel(const char *name); +void kloader_start_kernel(void); void kloader_stop(void); int kloader_validate_kpointer(void *p); diff --git a/soc/runtime/ksupport.c b/soc/runtime/ksupport.c index c7d465c26..da02bc4a0 100644 --- a/soc/runtime/ksupport.c +++ b/soc/runtime/ksupport.c @@ -184,51 +184,37 @@ void exception_handler(unsigned long vect, unsigned long *regs, int main(void); int main(void) { - struct msg_load_request *msg = mailbox_receive(); + struct msg_load_request *request = mailbox_receive(); + struct msg_load_reply load_reply = { + .type = MESSAGE_TYPE_LOAD_REPLY, + .error = NULL + }; - if(msg == NULL) { + if(request == NULL) { bridge_main(); while(1); } - if(msg->library != NULL) { - const char *error; - if(!dyld_load(msg->library, KERNELCPU_PAYLOAD_ADDRESS, - resolve_runtime_export, msg->library_info, &error)) { - struct msg_load_reply msg = { - .type = MESSAGE_TYPE_LOAD_REPLY, - .error = error - }; - mailbox_send(&msg); + if(request->library != NULL) { + if(!dyld_load(request->library, KERNELCPU_PAYLOAD_ADDRESS, + resolve_runtime_export, request->library_info, + &load_reply.error)) { + mailbox_send(&load_reply); while(1); } } - void (*kernel)(void) = NULL; - if(msg->kernel != NULL) { - kernel = dyld_lookup(msg->kernel, msg->library_info); - if(kernel == NULL) { - char error[256]; - snprintf(error, sizeof(error), - "kernel '%s' not found in library", msg->kernel); - struct msg_load_reply msg = { - .type = MESSAGE_TYPE_LOAD_REPLY, - .error = error - }; - mailbox_send(&msg); - while(1); - } - } + if(request->run_kernel) { + void (*kernel_init)() = request->library_info->init; - mailbox_acknowledge(); + mailbox_send_and_wait(&load_reply); + kernel_init(); - if(kernel) { - void (*run_closure)(void *) = msg->library_info->init; - run_closure(kernel); - - struct msg_base msg; - msg.type = MESSAGE_TYPE_FINISHED; - mailbox_send_and_wait(&msg); + struct msg_base finished_reply; + finished_reply.type = MESSAGE_TYPE_FINISHED; + mailbox_send_and_wait(&finished_reply); + } else { + mailbox_send(&load_reply); } while(1); diff --git a/soc/runtime/messages.h b/soc/runtime/messages.h index 4b633a5d5..cb0c9c20b 100644 --- a/soc/runtime/messages.h +++ b/soc/runtime/messages.h @@ -39,7 +39,7 @@ struct msg_base { struct msg_load_request { void *library; struct dyld_info *library_info; - const char *kernel; + int run_kernel; }; struct msg_load_reply { diff --git a/soc/runtime/session.c b/soc/runtime/session.c index 511cd8353..bc4ad4545 100644 --- a/soc/runtime/session.c +++ b/soc/runtime/session.c @@ -184,21 +184,8 @@ static int process_input(void) break; } - if((buffer_in_index + 1) > BUFFER_OUT_SIZE) { - log("Kernel name too long"); - buffer_out[8] = REMOTEMSG_TYPE_KERNEL_STARTUP_FAILED; - submit_output(9); - break; - } - buffer_in[buffer_in_index] = 0; - watchdog_init(); - if(!kloader_start_kernel((char *)&buffer_in[9])) { - log("Failed to find kernel entry point '%s' in object", &buffer_in[9]); - buffer_out[8] = REMOTEMSG_TYPE_KERNEL_STARTUP_FAILED; - submit_output(9); - break; - } + kloader_start_kernel(); user_kernel_state = USER_KERNEL_RUNNING; break; From b6e2613f7723114f7f3dfbed207bd953ff2ca31b Mon Sep 17 00:00:00 2001 From: whitequark Date: Fri, 7 Aug 2015 11:03:36 +0300 Subject: [PATCH 179/369] runtime: avoid spurious error messages. --- soc/runtime/kloader.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/soc/runtime/kloader.c b/soc/runtime/kloader.c index 547907728..5d1cb0a32 100644 --- a/soc/runtime/kloader.c +++ b/soc/runtime/kloader.c @@ -41,6 +41,8 @@ static int load_or_start_kernel(void *library, int run_kernel) start_kernel_cpu(&request); struct msg_load_reply *reply = mailbox_wait_and_receive(); + mailbox_acknowledge(); + if(reply->type != MESSAGE_TYPE_LOAD_REPLY) { log("BUG: unexpected reply to load/run request"); return 0; @@ -51,8 +53,6 @@ static int load_or_start_kernel(void *library, int run_kernel) return 0; } - mailbox_acknowledge(); - return 1; } From 353f454a29e239aa1a02cd6c2428620134050d2d Mon Sep 17 00:00:00 2001 From: whitequark Date: Fri, 7 Aug 2015 11:44:49 +0300 Subject: [PATCH 180/369] Add basic support for embedded functions with new compiler. --- artiq/compiler/__init__.py | 1 + artiq/compiler/asttyped.py | 2 + artiq/compiler/embedding.py | 157 ++++++++++++++++++ .../compiler/transforms/asttyped_rewriter.py | 7 +- artiq/coredevice/comm_generic.py | 23 +-- artiq/coredevice/core.py | 125 ++++---------- artiq/coredevice/exceptions.py | 33 ++++ artiq/coredevice/rpc_wrapper.py | 40 ----- artiq/coredevice/runtime.py | 78 --------- artiq/coredevice/runtime_exceptions.py | 69 -------- artiq/language/core.py | 82 +++++---- 11 files changed, 278 insertions(+), 339 deletions(-) create mode 100644 artiq/compiler/embedding.py create mode 100644 artiq/coredevice/exceptions.py delete mode 100644 artiq/coredevice/rpc_wrapper.py delete mode 100644 artiq/coredevice/runtime_exceptions.py diff --git a/artiq/compiler/__init__.py b/artiq/compiler/__init__.py index 1d187a71b..0fabfd4ad 100644 --- a/artiq/compiler/__init__.py +++ b/artiq/compiler/__init__.py @@ -1 +1,2 @@ from .module import Module, Source +from .embedding import Stitcher diff --git a/artiq/compiler/asttyped.py b/artiq/compiler/asttyped.py index e24003aba..f15726579 100644 --- a/artiq/compiler/asttyped.py +++ b/artiq/compiler/asttyped.py @@ -93,3 +93,5 @@ class YieldFromT(ast.YieldFrom, commontyped): # 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/embedding.py b/artiq/compiler/embedding.py new file mode 100644 index 000000000..a719c3a31 --- /dev/null +++ b/artiq/compiler/embedding.py @@ -0,0 +1,157 @@ +""" +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 inspect +from pythonparser import ast, source, diagnostic, parse_buffer +from . import types, builtins, asttyped, prelude +from .transforms import ASTTypedRewriter, Inferencer + + +class ASTSynthesizer: + def __init__(self): + self.source = "" + self.source_buffer = source.Buffer(self.source, "") + + 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) + + def quote(self, value): + """Construct an AST fragment equal to `value`.""" + if value in (None, True, False): + if node.value is True or node.value is False: + typ = builtins.TBool() + elif node.value is None: + typ = builtins.TNone() + return asttyped.NameConstantT(value=value, type=typ, + loc=self._add(repr(value))) + 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, list): + begin_loc = self._add("[") + elts = [] + for index, elt in value: + elts.append(self.quote(elt)) + if index < len(value) - 1: + self._add(", ") + end_loc = self._add("]") + return asttyped.ListT(elts=elts, ctx=None, type=types.TVar(), + begin_loc=begin_loc, end_loc=end_loc, + loc=begin_loc.join(end_loc)) + else: + raise "no" + # return asttyped.QuoteT(value=value, type=types.TVar()) + + def call(self, function_node, args, kwargs): + """ + Construct an AST fragment calling a function specified by + an AST node `function_node`, with given arguments. + """ + arg_nodes = [] + kwarg_nodes = [] + kwarg_locs = [] + + name_loc = self._add(function_node.name) + 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(")") + + return asttyped.CallT( + func=asttyped.NameT(id=function_node.name, ctx=None, + type=function_node.signature_type, + loc=name_loc), + 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(), + begin_loc=begin_loc, end_loc=end_loc, star_loc=None, dstar_loc=None, + loc=name_loc.join(end_loc)) + +class StitchingASTTypedRewriter(ASTTypedRewriter): + pass + +class Stitcher: + def __init__(self, engine=None): + if engine is None: + self.engine = diagnostic.Engine(all_errors_are_fatal=True) + else: + self.engine = engine + + self.asttyped_rewriter = StitchingASTTypedRewriter( + engine=self.engine, globals=prelude.globals()) + self.inferencer = Inferencer(engine=self.engine) + + self.name = "stitched" + self.typedtree = None + self.globals = self.asttyped_rewriter.globals + + self.rpc_map = {} + + def _iterate(self): + # Iterate inference to fixed point. + self.inference_finished = False + while not self.inference_finished: + self.inference_finished = True + self.inferencer.visit(self.typedtree) + + def _parse_embedded_function(self, function): + if not hasattr(function, "artiq_embedded"): + raise ValueError("{} is not an embedded function".format(repr(function))) + + # Extract function source. + embedded_function = function.artiq_embedded.function + source_code = inspect.getsource(embedded_function) + filename = embedded_function.__code__.co_filename + first_line = embedded_function.__code__.co_firstlineno + + # Parse. + source_buffer = source.Buffer(source_code, filename, first_line) + parsetree, comments = parse_buffer(source_buffer, engine=self.engine) + + # Rewrite into typed form. + typedtree = self.asttyped_rewriter.visit(parsetree) + + return typedtree, typedtree.body[0] + + def stitch_call(self, function, args, kwargs): + self.typedtree, function_node = self._parse_embedded_function(function) + + # We synthesize fake source code for the initial call so that + # diagnostics would have something meaningful to display to the user. + synthesizer = ASTSynthesizer() + call_node = synthesizer.call(function_node, args, kwargs) + synthesizer.finalize() + self.typedtree.body.append(call_node) + + self._iterate() diff --git a/artiq/compiler/transforms/asttyped_rewriter.py b/artiq/compiler/transforms/asttyped_rewriter.py index cc0de6b2c..85b96e694 100644 --- a/artiq/compiler/transforms/asttyped_rewriter.py +++ b/artiq/compiler/transforms/asttyped_rewriter.py @@ -190,10 +190,15 @@ class ASTTypedRewriter(algorithm.Transformer): self.globals = None self.env_stack = [globals] - def _find_name(self, name, loc): + def _try_find_name(self, name): 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", "name '{name}' is not bound to anything", {"name":name}, loc) self.engine.process(diag) diff --git a/artiq/coredevice/comm_generic.py b/artiq/coredevice/comm_generic.py index b538c33f7..da5d91017 100644 --- a/artiq/coredevice/comm_generic.py +++ b/artiq/coredevice/comm_generic.py @@ -3,9 +3,7 @@ import logging from enum import Enum from fractions import Fraction -from artiq.coredevice import runtime_exceptions from artiq.language import core as core_language -from artiq.coredevice.rpc_wrapper import RPCWrapper logger = logging.getLogger(__name__) @@ -198,35 +196,28 @@ class CommGeneric: else: r.append(self._receive_rpc_value(type_tag)) - def _serve_rpc(self, rpc_wrapper, rpc_map, user_exception_map): + def _serve_rpc(self, rpc_map): rpc_num = struct.unpack(">l", self.read(4))[0] args = self._receive_rpc_values() logger.debug("rpc service: %d %r", rpc_num, args) - eid, r = rpc_wrapper.run_rpc( - user_exception_map, rpc_map[rpc_num], args) + eid, r = rpc_wrapper.run_rpc(rpc_map[rpc_num], args) self._write_header(9+2*4, _H2DMsgType.RPC_REPLY) self.write(struct.pack(">ll", eid, r)) logger.debug("rpc service: %d %r == %r (eid %d)", rpc_num, args, r, eid) - def _serve_exception(self, rpc_wrapper, user_exception_map): + def _serve_exception(self): eid, p0, p1, p2 = struct.unpack(">lqqq", self.read(4+3*8)) rpc_wrapper.filter_rpc_exception(eid) - if eid < core_language.first_user_eid: - exception = runtime_exceptions.exception_map[eid] - raise exception(self.core, p0, p1, p2) - else: - exception = user_exception_map[eid] - raise exception + raise exception(self.core, p0, p1, p2) - def serve(self, rpc_map, user_exception_map): - rpc_wrapper = RPCWrapper() + def serve(self, rpc_map): while True: _, ty = self._read_header() if ty == _D2HMsgType.RPC_REQUEST: - self._serve_rpc(rpc_wrapper, rpc_map, user_exception_map) + self._serve_rpc(rpc_map) elif ty == _D2HMsgType.KERNEL_EXCEPTION: - self._serve_exception(rpc_wrapper, user_exception_map) + self._serve_exception() elif ty == _D2HMsgType.KERNEL_FINISHED: return else: diff --git a/artiq/coredevice/core.py b/artiq/coredevice/core.py index 536a10054..7367f0529 100644 --- a/artiq/coredevice/core.py +++ b/artiq/coredevice/core.py @@ -1,50 +1,20 @@ -import os +import os, sys, tempfile + +from pythonparser import diagnostic from artiq.language.core import * from artiq.language.units import ns -from artiq.transforms.inline import inline -from artiq.transforms.quantize_time import quantize_time -from artiq.transforms.remove_inter_assigns import remove_inter_assigns -from artiq.transforms.fold_constants import fold_constants -from artiq.transforms.remove_dead_code import remove_dead_code -from artiq.transforms.unroll_loops import unroll_loops -from artiq.transforms.interleave import interleave -from artiq.transforms.lower_time import lower_time -from artiq.transforms.unparse import unparse +from artiq.compiler import Stitcher, Module +from artiq.compiler.targets import OR1KTarget -from artiq.coredevice.runtime import Runtime - -from artiq.py2llvm import get_runtime_binary +# Import for side effects (creating the exception classes). +from artiq.coredevice import exceptions -def _announce_unparse(label, node): - print("*** Unparsing: "+label) - print(unparse(node)) - - -def _make_debug_unparse(final): - try: - env = os.environ["ARTIQ_UNPARSE"] - except KeyError: - env = "" - selected_labels = set(env.split()) - if "all" in selected_labels: - return _announce_unparse - else: - if "final" in selected_labels: - selected_labels.add(final) - - def _filtered_unparse(label, node): - if label in selected_labels: - _announce_unparse(label, node) - return _filtered_unparse - - -def _no_debug_unparse(label, node): +class CompileError(Exception): pass - class Core: def __init__(self, dmgr, ref_period=8*ns, external_clock=False): self.comm = dmgr.get("comm") @@ -54,69 +24,40 @@ class Core: self.first_run = True self.core = self self.comm.core = self - self.runtime = Runtime() - def transform_stack(self, func_def, rpc_map, exception_map, - debug_unparse=_no_debug_unparse): - remove_inter_assigns(func_def) - debug_unparse("remove_inter_assigns_1", func_def) + def compile(self, function, args, kwargs, with_attr_writeback=True): + try: + engine = diagnostic.Engine(all_errors_are_fatal=True) - quantize_time(func_def, self.ref_period) - debug_unparse("quantize_time", func_def) + stitcher = Stitcher(engine=engine) + stitcher.stitch_call(function, args, kwargs) - fold_constants(func_def) - debug_unparse("fold_constants_1", func_def) + module = Module(stitcher) + library = OR1KTarget().compile_and_link([module]) - unroll_loops(func_def, 500) - debug_unparse("unroll_loops", func_def) + return library, stitcher.rpc_map + except diagnostic.Error as error: + print("\n".join(error.diagnostic.render(colored=True)), file=sys.stderr) + raise CompileError() from error - interleave(func_def) - debug_unparse("interleave", func_def) - - lower_time(func_def) - debug_unparse("lower_time", func_def) - - remove_inter_assigns(func_def) - debug_unparse("remove_inter_assigns_2", func_def) - - fold_constants(func_def) - debug_unparse("fold_constants_2", func_def) - - remove_dead_code(func_def) - debug_unparse("remove_dead_code_1", func_def) - - remove_inter_assigns(func_def) - debug_unparse("remove_inter_assigns_3", func_def) - - fold_constants(func_def) - debug_unparse("fold_constants_3", func_def) - - remove_dead_code(func_def) - debug_unparse("remove_dead_code_2", func_def) - - def compile(self, k_function, k_args, k_kwargs, with_attr_writeback=True): - debug_unparse = _make_debug_unparse("remove_dead_code_2") - - func_def, rpc_map, exception_map = inline( - self, k_function, k_args, k_kwargs, with_attr_writeback) - debug_unparse("inline", func_def) - self.transform_stack(func_def, rpc_map, exception_map, debug_unparse) - - binary = get_runtime_binary(self.runtime, func_def) - - return binary, rpc_map, exception_map - - def run(self, k_function, k_args, k_kwargs): + def run(self, function, args, kwargs): if self.first_run: self.comm.check_ident() self.comm.switch_clock(self.external_clock) + self.first_run = False - binary, rpc_map, exception_map = self.compile( - k_function, k_args, k_kwargs) - self.comm.load(binary) - self.comm.run(k_function.__name__) - self.comm.serve(rpc_map, exception_map) - self.first_run = False + kernel_library, rpc_map = self.compile(function, args, kwargs) + + try: + self.comm.load(kernel_library) + except Exception as error: + shlib_temp = tempfile.NamedTemporaryFile(suffix=".so", delete=False) + shlib_temp.write(kernel_library) + shlib_temp.close() + raise RuntimeError("shared library dumped to {}".format(shlib_temp.name)) from error + + self.comm.run() + self.comm.serve(rpc_map) @kernel def get_rtio_counter_mu(self): diff --git a/artiq/coredevice/exceptions.py b/artiq/coredevice/exceptions.py new file mode 100644 index 000000000..05d475908 --- /dev/null +++ b/artiq/coredevice/exceptions.py @@ -0,0 +1,33 @@ +from artiq.language.core import ARTIQException + + +class InternalError(ARTIQException): + """Raised when the runtime encounters an internal error condition.""" + +class RTIOUnderflow(ARTIQException): + """Raised when the CPU fails to submit a RTIO event early enough + (with respect to the event's timestamp). + + The offending event is discarded and the RTIO core keeps operating. + """ + +class RTIOSequenceError(ARTIQException): + """Raised when an event is submitted on a given channel with a timestamp + not larger than the previous one. + + The offending event is discarded and the RTIO core keeps operating. + """ + +class RTIOOverflow(ARTIQException): + """Raised when at least one event could not be registered into the RTIO + input FIFO because it was full (CPU not reading fast enough). + + This does not interrupt operations further than cancelling the current + read attempt and discarding some events. Reading can be reattempted after + the exception is caught, and events will be partially retrieved. + """ + +class DDSBatchError(ARTIQException): + """Raised when attempting to start a DDS batch while already in a batch, + or when too many commands are batched. + """ diff --git a/artiq/coredevice/rpc_wrapper.py b/artiq/coredevice/rpc_wrapper.py deleted file mode 100644 index eeae17286..000000000 --- a/artiq/coredevice/rpc_wrapper.py +++ /dev/null @@ -1,40 +0,0 @@ -from artiq.coredevice.runtime_exceptions import exception_map, _RPCException - - -def _lookup_exception(d, e): - for eid, exception in d.items(): - if isinstance(e, exception): - return eid - return 0 - - -class RPCWrapper: - def __init__(self): - self.last_exception = None - - def run_rpc(self, user_exception_map, fn, args): - eid = 0 - r = None - - try: - r = fn(*args) - except Exception as e: - eid = _lookup_exception(user_exception_map, e) - if not eid: - eid = _lookup_exception(exception_map, e) - if eid: - self.last_exception = None - else: - self.last_exception = e - eid = _RPCException.eid - - if r is None: - r = 0 - else: - r = int(r) - - return eid, r - - def filter_rpc_exception(self, eid): - if eid == _RPCException.eid: - raise self.last_exception diff --git a/artiq/coredevice/runtime.py b/artiq/coredevice/runtime.py index 84a6bb386..e716aecfc 100644 --- a/artiq/coredevice/runtime.py +++ b/artiq/coredevice/runtime.py @@ -3,14 +3,8 @@ import os import llvmlite_or1k.ir as ll import llvmlite_or1k.binding as llvm -from artiq.py2llvm import base_types, fractions, lists from artiq.language import units - -llvm.initialize() -llvm.initialize_all_targets() -llvm.initialize_all_asmprinters() - _syscalls = { "now_init": "n:I", "now_save": "I:n", @@ -97,27 +91,6 @@ class LinkInterface: self.syscalls[func_name] = ll.Function( llvm_module, func_type, "__syscall_" + func_name) - # exception handling - func_type = ll.FunctionType(ll.IntType(32), - [ll.PointerType(ll.IntType(8))]) - self.eh_setjmp = ll.Function(llvm_module, func_type, - "__eh_setjmp") - self.eh_setjmp.attributes.add("nounwind") - self.eh_setjmp.attributes.add("returns_twice") - - func_type = ll.FunctionType(ll.PointerType(ll.IntType(8)), []) - self.eh_push = ll.Function(llvm_module, func_type, "__eh_push") - - func_type = ll.FunctionType(ll.VoidType(), [ll.IntType(32)]) - self.eh_pop = ll.Function(llvm_module, func_type, "__eh_pop") - - func_type = ll.FunctionType(ll.IntType(32), []) - self.eh_getid = ll.Function(llvm_module, func_type, "__eh_getid") - - func_type = ll.FunctionType(ll.VoidType(), [ll.IntType(32)]) - self.eh_raise = ll.Function(llvm_module, func_type, "__eh_raise") - self.eh_raise.attributes.add("noreturn") - def _build_rpc(self, args, builder): r = base_types.VInt() if builder is not None: @@ -159,54 +132,3 @@ class LinkInterface: return self._build_rpc(args, builder) else: return self._build_regular_syscall(syscall_name, args, builder) - - def build_catch(self, builder): - jmpbuf = builder.call(self.eh_push, []) - exception_occured = builder.call(self.eh_setjmp, [jmpbuf]) - return builder.icmp_signed("!=", - exception_occured, - ll.Constant(ll.IntType(32), 0)) - - def build_pop(self, builder, levels): - builder.call(self.eh_pop, [ll.Constant(ll.IntType(32), levels)]) - - def build_getid(self, builder): - return builder.call(self.eh_getid, []) - - def build_raise(self, builder, eid): - builder.call(self.eh_raise, [eid]) - - -def _debug_dump_obj(obj): - try: - env = os.environ["ARTIQ_DUMP_OBJECT"] - except KeyError: - return - - for i in range(1000): - filename = "{}_{:03d}.elf".format(env, i) - try: - f = open(filename, "xb") - except FileExistsError: - pass - else: - f.write(obj) - f.close() - return - raise IOError - - -class Runtime(LinkInterface): - def __init__(self): - self.cpu_type = "or1k" - # allow 1ms for all initial DDS programming - self.warmup_time = 1*units.ms - - def emit_object(self): - tm = llvm.Target.from_triple(self.cpu_type).create_target_machine() - obj = tm.emit_object(self.module.llvm_module_ref) - _debug_dump_obj(obj) - return obj - - def __repr__(self): - return "".format(self.cpu_type) diff --git a/artiq/coredevice/runtime_exceptions.py b/artiq/coredevice/runtime_exceptions.py deleted file mode 100644 index aa9a2c1c5..000000000 --- a/artiq/coredevice/runtime_exceptions.py +++ /dev/null @@ -1,69 +0,0 @@ -import inspect - -from artiq.language.core import RuntimeException - - -# Must be kept in sync with soc/runtime/exceptions.h - -class InternalError(RuntimeException): - """Raised when the runtime encounters an internal error condition.""" - eid = 1 - - -class _RPCException(RuntimeException): - eid = 2 - - -class RTIOUnderflow(RuntimeException): - """Raised when the CPU fails to submit a RTIO event early enough - (with respect to the event's timestamp). - - The offending event is discarded and the RTIO core keeps operating. - """ - eid = 3 - - def __str__(self): - return "at {} on channel {}, violation {}".format( - self.p0*self.core.ref_period, - self.p1, - (self.p2 - self.p0)*self.core.ref_period) - - -class RTIOSequenceError(RuntimeException): - """Raised when an event is submitted on a given channel with a timestamp - not larger than the previous one. - - The offending event is discarded and the RTIO core keeps operating. - """ - eid = 4 - - def __str__(self): - return "at {} on channel {}".format(self.p0*self.core.ref_period, - self.p1) - - -class RTIOOverflow(RuntimeException): - """Raised when at least one event could not be registered into the RTIO - input FIFO because it was full (CPU not reading fast enough). - - This does not interrupt operations further than cancelling the current - read attempt and discarding some events. Reading can be reattempted after - the exception is caught, and events will be partially retrieved. - """ - eid = 5 - - def __str__(self): - return "on channel {}".format(self.p0) - - -class DDSBatchError(RuntimeException): - """Raised when attempting to start a DDS batch while already in a batch, - or when too many commands are batched. - """ - eid = 6 - - -exception_map = {e.eid: e for e in globals().values() - if inspect.isclass(e) - and issubclass(e, RuntimeException) - and hasattr(e, "eid")} diff --git a/artiq/language/core.py b/artiq/language/core.py index a4ca0b883..d8e24a552 100644 --- a/artiq/language/core.py +++ b/artiq/language/core.py @@ -8,7 +8,7 @@ from functools import wraps __all__ = ["int64", "round64", "kernel", "portable", "set_time_manager", "set_syscall_manager", "set_watchdog_factory", - "RuntimeException", "EncodedException"] + "ARTIQException"] # global namespace for kernels kernel_globals = ("sequential", "parallel", @@ -77,7 +77,7 @@ def round64(x): return int64(round(x)) -_KernelFunctionInfo = namedtuple("_KernelFunctionInfo", "core_name k_function") +_ARTIQEmbeddedInfo = namedtuple("_ARTIQEmbeddedInfo", "core_name function") def kernel(arg): @@ -100,25 +100,19 @@ def kernel(arg): specifies the name of the attribute to use as core device driver. """ if isinstance(arg, str): - def real_decorator(k_function): - @wraps(k_function) - def run_on_core(exp, *k_args, **k_kwargs): - return getattr(exp, arg).run(k_function, - ((exp,) + k_args), k_kwargs) - run_on_core.k_function_info = _KernelFunctionInfo( - core_name=arg, k_function=k_function) + def inner_decorator(function): + @wraps(function) + def run_on_core(self, *k_args, **k_kwargs): + return getattr(self, arg).run(function, ((self,) + k_args), k_kwargs) + run_on_core.artiq_embedded = _ARTIQEmbeddedInfo( + core_name=arg, function=function) return run_on_core - return real_decorator + return inner_decorator else: - @wraps(arg) - def run_on_core(exp, *k_args, **k_kwargs): - return exp.core.run(arg, ((exp,) + k_args), k_kwargs) - run_on_core.k_function_info = _KernelFunctionInfo( - core_name="core", k_function=arg) - return run_on_core + return kernel("core")(arg) -def portable(f): +def portable(function): """This decorator marks a function for execution on the same device as its caller. @@ -127,8 +121,8 @@ def portable(f): core device). A decorated function called from a kernel will be executed on the core device (no RPC). """ - f.k_function_info = _KernelFunctionInfo(core_name="", k_function=f) - return f + function.artiq_embedded = _ARTIQEmbeddedInfo(core_name="", function=function) + return function class _DummyTimeManager: @@ -280,32 +274,34 @@ def watchdog(timeout): return _watchdog_factory(timeout) -_encoded_exceptions = dict() +class ARTIQException(Exception): + """Base class for exceptions raised or passed through the core device.""" + # Try and create an instance of the specific class, if one exists. + def __new__(cls, name, message, params): + def find_subclass(cls): + if cls.__name__ == name: + return cls + else: + for subclass in cls.__subclasses__(): + cls = find_subclass(subclass) + if cls is not None: + return cls -def EncodedException(eid): - """Represents exceptions on the core device, which are identified - by a single number.""" - try: - return _encoded_exceptions[eid] - except KeyError: - class EncodedException(Exception): - def __init__(self): - Exception.__init__(self, eid) - _encoded_exceptions[eid] = EncodedException - return EncodedException + more_specific_cls = find_subclass(cls) + if more_specific_cls is None: + more_specific_cls = cls + exn = Exception.__new__(more_specific_cls) + exn.__init__(name, message, params) + return exn -class RuntimeException(Exception): - """Base class for all exceptions used by the device runtime. - Those exceptions are defined in ``artiq.coredevice.runtime_exceptions``. - """ - def __init__(self, core, p0, p1, p2): - Exception.__init__(self) - self.core = core - self.p0 = p0 - self.p1 = p1 - self.p2 = p2 + def __init__(self, name, message, params): + Exception.__init__(self, name, message, *params) + self.name, self.message, self.params = name, message, params - -first_user_eid = 1024 + def __str__(self): + if type(self).__name__ == self.name: + return self.message.format(*self.params) + else: + return "({}) {}".format(self.name, self.message.format(*self.params)) From 50448ef554a53b14ba2d5e1260d25b5780459598 Mon Sep 17 00:00:00 2001 From: whitequark Date: Fri, 7 Aug 2015 13:20:29 +0300 Subject: [PATCH 181/369] Add support for referring to host values in embedded functions. --- artiq/compiler/embedding.py | 124 ++++++++++++++---- artiq/compiler/module.py | 5 +- .../compiler/transforms/artiq_ir_generator.py | 2 +- .../compiler/transforms/asttyped_rewriter.py | 4 +- 4 files changed, 109 insertions(+), 26 deletions(-) diff --git a/artiq/compiler/embedding.py b/artiq/compiler/embedding.py index a719c3a31..74498d436 100644 --- a/artiq/compiler/embedding.py +++ b/artiq/compiler/embedding.py @@ -5,7 +5,7 @@ the references to the host objects and translates the functions annotated as ``@kernel`` when they are referenced. """ -import inspect +import inspect, os from pythonparser import ast, source, diagnostic, parse_buffer from . import types, builtins, asttyped, prelude from .transforms import ASTTypedRewriter, Inferencer @@ -28,11 +28,12 @@ class ASTSynthesizer: def quote(self, value): """Construct an AST fragment equal to `value`.""" - if value in (None, True, False): - if node.value is True or node.value is False: - typ = builtins.TBool() - elif node.value is None: - typ = builtins.TNone() + if value is None: + typ = builtins.TNone() + return asttyped.NameConstantT(value=value, type=typ, + loc=self._add(repr(value))) + elif value is True or value is False: + typ = builtins.TBool() return asttyped.NameConstantT(value=value, type=typ, loc=self._add(repr(value))) elif isinstance(value, (int, float)): @@ -45,12 +46,12 @@ class ASTSynthesizer: elif isinstance(value, list): begin_loc = self._add("[") elts = [] - for index, elt in value: + for index, elt in enumerate(value): elts.append(self.quote(elt)) if index < len(value) - 1: self._add(", ") end_loc = self._add("]") - return asttyped.ListT(elts=elts, ctx=None, type=types.TVar(), + return asttyped.ListT(elts=elts, ctx=None, type=builtins.TList(), begin_loc=begin_loc, end_loc=end_loc, loc=begin_loc.join(end_loc)) else: @@ -99,7 +100,43 @@ class ASTSynthesizer: loc=name_loc.join(end_loc)) class StitchingASTTypedRewriter(ASTTypedRewriter): - pass + def __init__(self, engine, prelude, globals, host_environment, quote_function): + super().__init__(engine, prelude) + self.globals = globals + self.env_stack.append(self.globals) + + self.host_environment = host_environment + self.quote_function = quote_function + + 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 in self.host_environment: + value = self.host_environment[node.id] + if inspect.isfunction(value): + # It's a function. We need to translate the function and insert + # a reference to it. + function_name = self.quote_function(value) + return asttyped.NameT(id=function_name, ctx=None, + type=self.globals[function_name], + loc=node.loc) + + else: + # It's just a value. Quote it. + synthesizer = ASTSynthesizer() + node = synthesizer.quote(value) + synthesizer.finalize() + return node + else: + diag = diagnostic.Diagnostic("fatal", + "name '{name}' is not bound to anything", {"name":node.id}, + node.loc) + self.engine.process(diag) class Stitcher: def __init__(self, engine=None): @@ -108,24 +145,30 @@ class Stitcher: else: self.engine = engine - self.asttyped_rewriter = StitchingASTTypedRewriter( - engine=self.engine, globals=prelude.globals()) - self.inferencer = Inferencer(engine=self.engine) + self.name = "" + self.typedtree = [] + self.prelude = prelude.globals() + self.globals = {} - self.name = "stitched" - self.typedtree = None - self.globals = self.asttyped_rewriter.globals + self.functions = {} self.rpc_map = {} def _iterate(self): + inferencer = Inferencer(engine=self.engine) + # Iterate inference to fixed point. self.inference_finished = False while not self.inference_finished: self.inference_finished = True - self.inferencer.visit(self.typedtree) + inferencer.visit(self.typedtree) - def _parse_embedded_function(self, function): + # After we have found all functions, synthesize a module to hold them. + self.typedtree = asttyped.ModuleT( + typing_env=self.globals, globals_in_scope=set(), + body=self.typedtree, loc=None) + + def _quote_embedded_function(self, function): if not hasattr(function, "artiq_embedded"): raise ValueError("{} is not an embedded function".format(repr(function))) @@ -133,25 +176,62 @@ class Stitcher: embedded_function = function.artiq_embedded.function source_code = inspect.getsource(embedded_function) filename = embedded_function.__code__.co_filename + module_name, _ = os.path.splitext(os.path.basename(filename)) first_line = embedded_function.__code__.co_firstlineno + # 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)}) + # Parse. source_buffer = source.Buffer(source_code, filename, first_line) parsetree, comments = parse_buffer(source_buffer, engine=self.engine) + function_node = parsetree.body[0] + + # Mangle the name, since we put everything into a single module. + function_node.name = "{}.{}".format(module_name, function_node.name) + + # Normally, LocalExtractor would populate the typing environment + # of the module with the function name. However, since we run + # ASTTypedRewriter on the function node directly, we need to do it + # explicitly. + self.globals[function_node.name] = types.TVar() + + # Memoize the function before typing it to handle recursive + # invocations. + self.functions[function] = function_node.name # Rewrite into typed form. - typedtree = self.asttyped_rewriter.visit(parsetree) + asttyped_rewriter = StitchingASTTypedRewriter( + engine=self.engine, prelude=self.prelude, + globals=self.globals, host_environment=host_environment, + quote_function=self._quote_function) + return asttyped_rewriter.visit(function_node) - return typedtree, typedtree.body[0] + def _quote_function(self, function): + if function in self.functions: + return self.functions[function] + + # Insert the typed AST for the new function and restart inference. + # It doesn't really matter where we insert as long as it is before + # the final call. + function_node = self._quote_embedded_function(function) + self.typedtree.insert(0, function_node) + self.inference_finished = False + return function_node.name def stitch_call(self, function, args, kwargs): - self.typedtree, function_node = self._parse_embedded_function(function) + function_node = self._quote_embedded_function(function) + self.typedtree.append(function_node) - # We synthesize fake source code for the initial call so that + # We synthesize source code for the initial call so that # diagnostics would have something meaningful to display to the user. synthesizer = ASTSynthesizer() call_node = synthesizer.call(function_node, args, kwargs) synthesizer.finalize() - self.typedtree.body.append(call_node) + self.typedtree.append(call_node) self._iterate() diff --git a/artiq/compiler/module.py b/artiq/compiler/module.py index 2f62da1d8..d35a887c4 100644 --- a/artiq/compiler/module.py +++ b/artiq/compiler/module.py @@ -67,7 +67,10 @@ class Module: def entry_point(self): """Return the name of the function that is the entry point of this module.""" - return self.name + ".__modinit__" + if self.name != "": + return self.name + ".__modinit__" + else: + return "__modinit__" def __repr__(self): printer = types.TypePrinter() diff --git a/artiq/compiler/transforms/artiq_ir_generator.py b/artiq/compiler/transforms/artiq_ir_generator.py index 845ef3189..84bcbc6c5 100644 --- a/artiq/compiler/transforms/artiq_ir_generator.py +++ b/artiq/compiler/transforms/artiq_ir_generator.py @@ -70,7 +70,7 @@ class ARTIQIRGenerator(algorithm.Visitor): def __init__(self, module_name, engine): self.engine = engine self.functions = [] - self.name = [module_name] + self.name = [module_name] if module_name != "" else [] self.current_loc = None self.current_function = None self.current_globals = set() diff --git a/artiq/compiler/transforms/asttyped_rewriter.py b/artiq/compiler/transforms/asttyped_rewriter.py index 85b96e694..51b84efb0 100644 --- a/artiq/compiler/transforms/asttyped_rewriter.py +++ b/artiq/compiler/transforms/asttyped_rewriter.py @@ -185,10 +185,10 @@ class ASTTypedRewriter(algorithm.Transformer): via :class:`LocalExtractor`. """ - def __init__(self, engine, globals): + def __init__(self, engine, prelude): self.engine = engine self.globals = None - self.env_stack = [globals] + self.env_stack = [prelude] def _try_find_name(self, name): for typing_env in reversed(self.env_stack): From a7633f75c727a46b67f2bc2ba23baaba276a152d Mon Sep 17 00:00:00 2001 From: whitequark Date: Fri, 7 Aug 2015 13:56:18 +0300 Subject: [PATCH 182/369] Show origin of expansion in diagnostics for synthesized code. --- artiq/compiler/embedding.py | 8 +++++--- artiq/compiler/transforms/inferencer.py | 5 ++++- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/artiq/compiler/embedding.py b/artiq/compiler/embedding.py index 74498d436..4a744131d 100644 --- a/artiq/compiler/embedding.py +++ b/artiq/compiler/embedding.py @@ -12,9 +12,10 @@ from .transforms import ASTTypedRewriter, Inferencer class ASTSynthesizer: - def __init__(self): + def __init__(self, expanded_from=None): self.source = "" self.source_buffer = source.Buffer(self.source, "") + self.expanded_from = expanded_from def finalize(self): self.source_buffer.source = self.source @@ -24,7 +25,8 @@ class ASTSynthesizer: range_from = len(self.source) self.source += fragment range_to = len(self.source) - return source.Range(self.source_buffer, range_from, range_to) + return source.Range(self.source_buffer, range_from, range_to, + expanded_from=self.expanded_from) def quote(self, value): """Construct an AST fragment equal to `value`.""" @@ -128,7 +130,7 @@ class StitchingASTTypedRewriter(ASTTypedRewriter): else: # It's just a value. Quote it. - synthesizer = ASTSynthesizer() + synthesizer = ASTSynthesizer(expanded_from=node.loc) node = synthesizer.quote(value) synthesizer.finalize() return node diff --git a/artiq/compiler/transforms/inferencer.py b/artiq/compiler/transforms/inferencer.py index e0338dcdc..6df5d3eec 100644 --- a/artiq/compiler/transforms/inferencer.py +++ b/artiq/compiler/transforms/inferencer.py @@ -76,9 +76,12 @@ class Inferencer(algorithm.Visitor): 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, - node.loc, elt.loc, self._makenotes_elts(node.elts, "a list element")) + 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) From acc97a74f0aeeca5cd9dc242104a6350c8fadc83 Mon Sep 17 00:00:00 2001 From: whitequark Date: Fri, 7 Aug 2015 14:21:53 +0300 Subject: [PATCH 183/369] Fix compiler.module. --- artiq/compiler/module.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/artiq/compiler/module.py b/artiq/compiler/module.py index d35a887c4..7f77c04dd 100644 --- a/artiq/compiler/module.py +++ b/artiq/compiler/module.py @@ -22,7 +22,7 @@ class Source: self.name, _ = os.path.splitext(os.path.basename(source_buffer.name)) asttyped_rewriter = transforms.ASTTypedRewriter(engine=engine, - globals=prelude.globals()) + prelude=prelude.globals()) inferencer = transforms.Inferencer(engine=engine) self.parsetree, self.comments = parse_buffer(source_buffer, engine=engine) From d6ab567242c5e921feeb9e50c827c67924a69e8b Mon Sep 17 00:00:00 2001 From: whitequark Date: Fri, 7 Aug 2015 16:15:44 +0300 Subject: [PATCH 184/369] coredevice.comm_*: refactor. --- artiq/coredevice/comm_dummy.py | 2 +- artiq/coredevice/comm_generic.py | 314 ++++++++++++++++++++----------- artiq/coredevice/comm_serial.py | 1 + artiq/coredevice/comm_tcp.py | 1 + 4 files changed, 204 insertions(+), 114 deletions(-) diff --git a/artiq/coredevice/comm_dummy.py b/artiq/coredevice/comm_dummy.py index 5b0c35c46..072c9bdd8 100644 --- a/artiq/coredevice/comm_dummy.py +++ b/artiq/coredevice/comm_dummy.py @@ -3,7 +3,7 @@ from operator import itemgetter class Comm: def __init__(self, dmgr): - pass + super().__init__() def switch_clock(self, external): pass diff --git a/artiq/coredevice/comm_generic.py b/artiq/coredevice/comm_generic.py index da5d91017..b8ac7fa07 100644 --- a/artiq/coredevice/comm_generic.py +++ b/artiq/coredevice/comm_generic.py @@ -50,7 +50,11 @@ class UnsupportedDevice(Exception): class CommGeneric: - # methods for derived classes to implement + def __init__(self): + self._read_type = self._write_type = None + self._read_length = 0 + self._write_buffer = [] + def open(self): """Opens the communication channel. Must do nothing if already opened.""" @@ -70,167 +74,251 @@ class CommGeneric: """Writes exactly length bytes to the communication channel. The channel is assumed to be opened.""" raise NotImplementedError + + # + # Reader interface # def _read_header(self): self.open() + if self._read_length > 0: + raise IOError("Read underrun ({} bytes remaining)". + format(self._read_length)) + + # Wait for a synchronization sequence, 5a 5a 5a 5a. sync_count = 0 while sync_count < 4: - (c, ) = struct.unpack("B", self.read(1)) - if c == 0x5a: + (sync_byte, ) = struct.unpack("B", self.read(1)) + if sync_byte == 0x5a: sync_count += 1 else: sync_count = 0 - length = struct.unpack(">l", self.read(4))[0] - if not length: # inband connection close - raise OSError("Connection closed") - tyv = struct.unpack("B", self.read(1))[0] - ty = _D2HMsgType(tyv) - logger.debug("receiving message: type=%r length=%d", ty, length) - return length, ty - def _write_header(self, length, ty): + # Read message header. + (self._read_length, ) = struct.unpack(">l", self.read(4)) + if not self._read_length: # inband connection close + raise OSError("Connection closed") + + (raw_type, ) = struct.unpack("B", self.read(1)) + self._read_type = _D2HMsgType(raw_type) + + if self._read_length < 9: + raise IOError("Read overrun in message header ({} remaining)". + format(self._read_length)) + self._read_length -= 9 + + logger.debug("receiving message: type=%r length=%d", + self._read_type, self._read_length) + + def _read_expect(self, ty): + if self._read_type != ty: + raise IOError("Incorrect reply from device: {} (expected {})". + format(self._read_type, ty)) + + def _read_empty(self, ty): + self._read_header() + self._read_expect(ty) + + def _read_chunk(self, length): + if self._read_length < length: + raise IOError("Read overrun while trying to read {} bytes ({} remaining)". + format(length, self._read_length)) + + self._read_length -= length + return self.read(length) + + def _read_int8(self): + (value, ) = struct.unpack("B", self._read_chunk(1)) + return value + + def _read_int32(self): + (value, ) = struct.unpack(">l", self._read_chunk(4)) + return value + + def _read_int64(self): + (value, ) = struct.unpack(">q", self._read_chunk(8)) + return value + + def _read_float64(self): + (value, ) = struct.unpack(">d", self._read_chunk(8)) + return value + + # + # Writer interface + # + + def _write_header(self, ty): self.open() - logger.debug("sending message: type=%r length=%d", ty, length) - self.write(struct.pack(">ll", 0x5a5a5a5a, length)) - if ty is not None: - self.write(struct.pack("B", ty.value)) + + logger.debug("preparing to send message: type=%r", ty) + self._write_type = ty + self._write_buffer = [] + + def _write_flush(self): + # Calculate message size. + length = sum([len(chunk) for chunk in self._write_buffer]) + logger.debug("sending message: type=%r length=%d", self._write_type, length) + + # Write synchronization sequence, header and body. + self.write(struct.pack(">llB", 0x5a5a5a5a, + 9 + length, self._write_type.value)) + for chunk in self._write_buffer: + self.write(chunk) + + def _write_empty(self, ty): + self._write_header(ty) + self._write_flush() + + def _write_int8(self, value): + self._write_buffer.append(struct.pack("B", value)) + + def _write_int32(self, value): + self._write_buffer.append(struct.pack(">l", value)) + + def _write_int64(self, value): + self._write_buffer.append(struct.pack(">q", value)) + + def _write_float64(self, value): + self._write_buffer.append(struct.pack(">d", value)) + + def _write_string(self, value): + self._write_buffer.append(value) + + # + # Exported APIs + # def reset_session(self): - self._write_header(0, None) + self.write(struct.pack(">ll", 0x5a5a5a5a, 0)) def check_ident(self): - self._write_header(9, _H2DMsgType.IDENT_REQUEST) - _, ty = self._read_header() - if ty != _D2HMsgType.IDENT_REPLY: - raise IOError("Incorrect reply from device: {}".format(ty)) - (reply, ) = struct.unpack("B", self.read(1)) - runtime_id = chr(reply) - for i in range(3): - (reply, ) = struct.unpack("B", self.read(1)) - runtime_id += chr(reply) - if runtime_id != "AROR": + self._write_empty(_H2DMsgType.IDENT_REQUEST) + + self._read_header() + self._read_expect(_D2HMsgType.IDENT_REPLY) + runtime_id = self._read_chunk(4) + if runtime_id != b"AROR": raise UnsupportedDevice("Unsupported runtime ID: {}" .format(runtime_id)) def switch_clock(self, external): - self._write_header(10, _H2DMsgType.SWITCH_CLOCK) - self.write(struct.pack("B", int(external))) - _, ty = self._read_header() - if ty != _D2HMsgType.CLOCK_SWITCH_COMPLETED: - raise IOError("Incorrect reply from device: {}".format(ty)) + self._write_header(_H2DMsgType.SWITCH_CLOCK) + self._write_int8(external) + self._write_flush() - def load(self, kcode): - self._write_header(len(kcode) + 9, _H2DMsgType.LOAD_LIBRARY) - self.write(kcode) - _, ty = self._read_header() - if ty != _D2HMsgType.LOAD_COMPLETED: - raise IOError("Incorrect reply from device: "+str(ty)) + self._read_empty(_D2HMsgType.CLOCK_SWITCH_COMPLETED) + + def load(self, kernel_library): + self._write_header(_H2DMsgType.LOAD_LIBRARY) + self._write_string(kernel_library) + self._write_flush() + + self._read_empty(_D2HMsgType.LOAD_COMPLETED) def run(self): - self._write_header(9, _H2DMsgType.RUN_KERNEL) + self._write_empty(_H2DMsgType.RUN_KERNEL) logger.debug("running kernel") def flash_storage_read(self, key): - self._write_header(9+len(key), _H2DMsgType.FLASH_READ_REQUEST) - self.write(key) - length, ty = self._read_header() - if ty != _D2HMsgType.FLASH_READ_REPLY: - raise IOError("Incorrect reply from device: {}".format(ty)) - value = self.read(length - 9) - return value + self._write_header(_H2DMsgType.FLASH_READ_REQUEST) + self._write_string(key) + self._write_flush() + + self._read_header() + self._read_expect(_D2HMsgType.FLASH_READ_REPLY) + return self._read_chunk(self._read_length) def flash_storage_write(self, key, value): - self._write_header(9+len(key)+1+len(value), - _H2DMsgType.FLASH_WRITE_REQUEST) - self.write(key) - self.write(b"\x00") - self.write(value) - _, ty = self._read_header() - if ty != _D2HMsgType.FLASH_OK_REPLY: - if ty == _D2HMsgType.FLASH_ERROR_REPLY: - raise IOError("Flash storage is full") - else: - raise IOError("Incorrect reply from device: {}".format(ty)) + self._write_header(_H2DMsgType.FLASH_WRITE_REQUEST) + self._write_string(key) + self._write_string(b"\x00") + self._write_string(value) + self._write_flush() + + self._read_header() + if self._read_type == _D2HMsgType.FLASH_ERROR_REPLY: + raise IOError("Flash storage is full") + else: + self._read_expect(_D2HMsgType.FLASH_OK_REPLY) def flash_storage_erase(self): - self._write_header(9, _H2DMsgType.FLASH_ERASE_REQUEST) - _, ty = self._read_header() - if ty != _D2HMsgType.FLASH_OK_REPLY: - raise IOError("Incorrect reply from device: {}".format(ty)) + self._write_empty(_H2DMsgType.FLASH_ERASE_REQUEST) + + self._read_empty(_D2HMsgType.FLASH_OK_REPLY) def flash_storage_remove(self, key): - self._write_header(9+len(key), _H2DMsgType.FLASH_REMOVE_REQUEST) - self.write(key) - _, ty = self._read_header() - if ty != _D2HMsgType.FLASH_OK_REPLY: - raise IOError("Incorrect reply from device: {}".format(ty)) + self._write_header(_H2DMsgType.FLASH_REMOVE_REQUEST) + self._write_string(key) + self._write_flush() - def _receive_rpc_value(self, type_tag): - if type_tag == "n": + self._read_empty(_D2HMsgType.FLASH_OK_REPLY) + + def _receive_rpc_value(self, tag): + if tag == "n": return None - if type_tag == "b": - return bool(struct.unpack("B", self.read(1))[0]) - if type_tag == "i": - return struct.unpack(">l", self.read(4))[0] - if type_tag == "I": - return struct.unpack(">q", self.read(8))[0] - if type_tag == "f": - return struct.unpack(">d", self.read(8))[0] - if type_tag == "F": - n, d = struct.unpack(">qq", self.read(16)) - return Fraction(n, d) + elif tag == "b": + return bool(self._read_int8()) + elif tag == "i": + return self._read_int32() + elif tag == "I": + return self._read_int64() + elif tag == "f": + return self._read_float64() + elif tag == "F": + numerator = self._read_int64() + denominator = self._read_int64() + return Fraction(numerator, denominator) + elif tag == "l": + elt_tag = chr(self._read_int8()) + length = self._read_int32() + return [self._receive_rpc_value(elt_tag) for _ in range(length)] + else: + raise IOError("Unknown RPC value tag: {}", tag) def _receive_rpc_values(self): - r = [] + result = [] while True: - type_tag = chr(struct.unpack("B", self.read(1))[0]) - if type_tag == "\x00": - return r - elif type_tag == "l": - elt_type_tag = chr(struct.unpack("B", self.read(1))[0]) - length = struct.unpack(">l", self.read(4))[0] - r.append([self._receive_rpc_value(elt_type_tag) - for i in range(length)]) + tag = chr(self._read_int8()) + if tag == "\x00": + return result else: - r.append(self._receive_rpc_value(type_tag)) + result.append(self._receive_rpc_value(tag)) def _serve_rpc(self, rpc_map): - rpc_num = struct.unpack(">l", self.read(4))[0] + service = self._read_int32() args = self._receive_rpc_values() - logger.debug("rpc service: %d %r", rpc_num, args) - eid, r = rpc_wrapper.run_rpc(rpc_map[rpc_num], args) - self._write_header(9+2*4, _H2DMsgType.RPC_REPLY) - self.write(struct.pack(">ll", eid, r)) - logger.debug("rpc service: %d %r == %r (eid %d)", rpc_num, args, - r, eid) + logger.debug("rpc service: %d %r", service, args) + + eid, result = rpc_wrapper.run_rpc(rpc_map[rpc_num], args) + logger.debug("rpc service: %d %r == %r (eid %d)", service, args, + result, eid) + + self._write_header(_H2DMsgType.RPC_REPLY) + self._write_int32(eid) + self._write_int32(result) + self._write_flush() def _serve_exception(self): - eid, p0, p1, p2 = struct.unpack(">lqqq", self.read(4+3*8)) + eid = self._read_int32() + params = [self._read_int64() for _ in range(3)] rpc_wrapper.filter_rpc_exception(eid) - raise exception(self.core, p0, p1, p2) + raise exception(self.core, *params) def serve(self, rpc_map): while True: - _, ty = self._read_header() - if ty == _D2HMsgType.RPC_REQUEST: + self._read_header() + if self._read_type == _D2HMsgType.RPC_REQUEST: self._serve_rpc(rpc_map) - elif ty == _D2HMsgType.KERNEL_EXCEPTION: + elif self._read_type == _D2HMsgType.KERNEL_EXCEPTION: self._serve_exception() - elif ty == _D2HMsgType.KERNEL_FINISHED: - return else: - raise IOError("Incorrect request from device: "+str(ty)) + self._read_expect(_D2HMsgType.KERNEL_FINISHED) + return def get_log(self): - self._write_header(9, _H2DMsgType.LOG_REQUEST) - length, ty = self._read_header() - if ty != _D2HMsgType.LOG_REPLY: - raise IOError("Incorrect request from device: "+str(ty)) - r = "" - for i in range(length - 9): - c = struct.unpack("B", self.read(1))[0] - if c: - r += chr(c) - return r + self._write_empty(_H2DMsgType.LOG_REQUEST) + + self._read_header() + self._read_expect(_D2HMsgType.LOG_REPLY) + return self._read_chunk(self._read_length).decode('utf-8') diff --git a/artiq/coredevice/comm_serial.py b/artiq/coredevice/comm_serial.py index 70218da14..bf710c29a 100644 --- a/artiq/coredevice/comm_serial.py +++ b/artiq/coredevice/comm_serial.py @@ -10,6 +10,7 @@ logger = logging.getLogger(__name__) class Comm(CommGeneric): def __init__(self, dmgr, serial_dev, baud_rate=115200): + super().__init__() self.serial_dev = serial_dev self.baud_rate = baud_rate diff --git a/artiq/coredevice/comm_tcp.py b/artiq/coredevice/comm_tcp.py index eda672750..f5a97658d 100644 --- a/artiq/coredevice/comm_tcp.py +++ b/artiq/coredevice/comm_tcp.py @@ -9,6 +9,7 @@ logger = logging.getLogger(__name__) class Comm(CommGeneric): def __init__(self, dmgr, host, port=1381): + super().__init__() self.host = host self.port = port From ecdebc0b8a77113577dc5f764c652587aa1077ec Mon Sep 17 00:00:00 2001 From: whitequark Date: Sat, 8 Aug 2015 13:21:43 +0300 Subject: [PATCH 185/369] session.c: refactor. --- artiq/coredevice/comm_generic.py | 25 +- artiq/coredevice/comm_serial.py | 8 +- artiq/frontend/artiq_coretool.py | 11 +- soc/runtime/flash_storage.c | 14 +- soc/runtime/flash_storage.h | 6 +- soc/runtime/ksupport.h | 2 +- soc/runtime/main.c | 8 +- soc/runtime/net_server.c | 3 +- soc/runtime/session.c | 705 +++++++++++++++++-------------- soc/runtime/session.h | 9 +- 10 files changed, 437 insertions(+), 354 deletions(-) diff --git a/artiq/coredevice/comm_generic.py b/artiq/coredevice/comm_generic.py index b8ac7fa07..b386a7430 100644 --- a/artiq/coredevice/comm_generic.py +++ b/artiq/coredevice/comm_generic.py @@ -122,8 +122,9 @@ class CommGeneric: def _read_chunk(self, length): if self._read_length < length: - raise IOError("Read overrun while trying to read {} bytes ({} remaining)". - format(length, self._read_length)) + raise IOError("Read overrun while trying to read {} bytes ({} remaining)" + " in packet {}". + format(length, self._read_length, self._read_type)) self._read_length -= length return self.read(length) @@ -144,6 +145,12 @@ class CommGeneric: (value, ) = struct.unpack(">d", self._read_chunk(8)) return value + def _read_bytes(self): + return self._read_chunk(self._read_int32()) + + def _read_string(self): + return self._read_bytes()[:-1].decode('utf-8') + # # Writer interface # @@ -170,6 +177,9 @@ class CommGeneric: self._write_header(ty) self._write_flush() + def _write_chunk(self, chunk): + self._write_buffer.append(chunk) + def _write_int8(self, value): self._write_buffer.append(struct.pack("B", value)) @@ -182,9 +192,13 @@ class CommGeneric: def _write_float64(self, value): self._write_buffer.append(struct.pack(">d", value)) - def _write_string(self, value): + def _write_bytes(self, value): + self._write_int32(len(value)) self._write_buffer.append(value) + def _write_string(self, value): + self._write_bytes(value.encode("utf-8") + b"\0") + # # Exported APIs # @@ -211,7 +225,7 @@ class CommGeneric: def load(self, kernel_library): self._write_header(_H2DMsgType.LOAD_LIBRARY) - self._write_string(kernel_library) + self._write_chunk(kernel_library) self._write_flush() self._read_empty(_D2HMsgType.LOAD_COMPLETED) @@ -232,8 +246,7 @@ class CommGeneric: def flash_storage_write(self, key, value): self._write_header(_H2DMsgType.FLASH_WRITE_REQUEST) self._write_string(key) - self._write_string(b"\x00") - self._write_string(value) + self._write_bytes(value) self._write_flush() self._read_header() diff --git a/artiq/coredevice/comm_serial.py b/artiq/coredevice/comm_serial.py index bf710c29a..91eda3567 100644 --- a/artiq/coredevice/comm_serial.py +++ b/artiq/coredevice/comm_serial.py @@ -28,10 +28,10 @@ class Comm(CommGeneric): del self.port def read(self, length): - r = bytes() - while len(r) < length: - r += self.port.read(length - len(r)) - return r + result = bytes() + while len(result) < length: + result += self.port.read(length - len(result)) + return result def write(self, data): remaining = len(data) diff --git a/artiq/frontend/artiq_coretool.py b/artiq/frontend/artiq_coretool.py index 41649f03e..8da310b3e 100755 --- a/artiq/frontend/artiq_coretool.py +++ b/artiq/frontend/artiq_coretool.py @@ -26,7 +26,7 @@ def get_argparser(): # Configuration Read command p_read = subparsers.add_parser("cfg-read", help="read key from core device config") - p_read.add_argument("key", type=to_bytes, + p_read.add_argument("key", type=str, help="key to be read from core device config") # Configuration Write command @@ -34,11 +34,11 @@ def get_argparser(): help="write key-value records to core " "device config") p_write.add_argument("-s", "--string", nargs=2, action="append", - default=[], metavar=("KEY", "STRING"), type=to_bytes, + default=[], metavar=("KEY", "STRING"), type=str, help="key-value records to be written to core device " "config") p_write.add_argument("-f", "--file", nargs=2, action="append", - type=to_bytes, default=[], + type=str, default=[], metavar=("KEY", "FILENAME"), help="key and file whose content to be written to " "core device config") @@ -47,7 +47,7 @@ def get_argparser(): p_delete = subparsers.add_parser("cfg-delete", help="delete key from core device config") p_delete.add_argument("key", nargs=argparse.REMAINDER, - default=[], type=to_bytes, + default=[], type=str, help="key to be deleted from core device config") # Configuration Erase command @@ -61,6 +61,7 @@ def main(): dmgr = DeviceManager(FlatFileDB(args.ddb)) try: comm = dmgr.get("comm") + comm.check_ident() if args.action == "log": print(comm.get_log()) @@ -72,7 +73,7 @@ def main(): print(value) elif args.action == "cfg-write": for key, value in args.string: - comm.flash_storage_write(key, value) + comm.flash_storage_write(key, value.encode('utf-8')) for key, filename in args.file: with open(filename, "rb") as fi: comm.flash_storage_write(key, fi.read()) diff --git a/soc/runtime/flash_storage.c b/soc/runtime/flash_storage.c index 34bc3a508..a160409a8 100644 --- a/soc/runtime/flash_storage.c +++ b/soc/runtime/flash_storage.c @@ -115,7 +115,8 @@ static int is_empty(struct record *record) return record->value_len == 0; } -static int key_exists(char *buff, char *key, char *end, char accept_empty, struct record *found_record) +static int key_exists(char *buff, const char *key, char *end, char accept_empty, + struct record *found_record) { struct iter_state is; struct record iter_record; @@ -170,7 +171,7 @@ static char check_for_empty_records(char *buff) return 0; } -static unsigned int try_to_flush_duplicates(char *new_key, unsigned int buf_len) +static unsigned int try_to_flush_duplicates(const char *new_key, unsigned int buf_len) { unsigned int key_size, new_record_size, ret = 0, can_rollback = 0; struct record record, previous_record; @@ -210,7 +211,8 @@ static unsigned int try_to_flush_duplicates(char *new_key, unsigned int buf_len) return ret; } -static void write_at_offset(char *key, void *buffer, int buf_len, unsigned int sector_offset) +static void write_at_offset(const char *key, const void *buffer, + int buf_len, unsigned int sector_offset) { int key_len = strlen(key) + 1; unsigned int record_size = key_len + buf_len + sizeof(record_size); @@ -223,7 +225,7 @@ static void write_at_offset(char *key, void *buffer, int buf_len, unsigned int s } -int fs_write(char *key, void *buffer, unsigned int buf_len) +int fs_write(const char *key, const void *buffer, unsigned int buf_len) { struct record record; unsigned int key_size = strlen(key) + 1; @@ -269,7 +271,7 @@ void fs_erase(void) flush_cpu_dcache(); } -unsigned int fs_read(char *key, void *buffer, unsigned int buf_len, unsigned int *remain) +unsigned int fs_read(const char *key, void *buffer, unsigned int buf_len, unsigned int *remain) { unsigned int read_length = 0; struct iter_state is; @@ -295,7 +297,7 @@ unsigned int fs_read(char *key, void *buffer, unsigned int buf_len, unsigned int return read_length; } -void fs_remove(char *key) +void fs_remove(const char *key) { fs_write(key, NULL, 0); } diff --git a/soc/runtime/flash_storage.h b/soc/runtime/flash_storage.h index 9994fef37..e983de778 100644 --- a/soc/runtime/flash_storage.h +++ b/soc/runtime/flash_storage.h @@ -5,9 +5,9 @@ #ifndef __FLASH_STORAGE_H #define __FLASH_STORAGE_H -void fs_remove(char *key); +void fs_remove(const char *key); void fs_erase(void); -int fs_write(char *key, void *buffer, unsigned int buflen); -unsigned int fs_read(char *key, void *buffer, unsigned int buflen, unsigned int *remain); +int fs_write(const char *key, const void *buffer, unsigned int buflen); +unsigned int fs_read(const char *key, void *buffer, unsigned int buflen, unsigned int *remain); #endif /* __FLASH_STORAGE_H */ diff --git a/soc/runtime/ksupport.h b/soc/runtime/ksupport.h index b78f934f8..47b9c3a1d 100644 --- a/soc/runtime/ksupport.h +++ b/soc/runtime/ksupport.h @@ -5,7 +5,7 @@ long long int now_init(void); void now_save(long long int now); int watchdog_set(int ms); void watchdog_clear(int id); -int rpc(int rpc_num, ...); +int rpc(int service, ...); void lognonl(const char *fmt, ...); void log(const char *fmt, ...); diff --git a/soc/runtime/main.c b/soc/runtime/main.c index bdd9d4b8c..e2c3691f5 100644 --- a/soc/runtime/main.c +++ b/soc/runtime/main.c @@ -189,12 +189,12 @@ static void serial_service(void) session_poll((void **)&txdata, &txlen); if(txlen > 0) { - for(i=0;i sndbuf) len = sndbuf; tcp_write(active_pcb, data, len, 0); - session_ack_data(len); } if(len < 0) net_server_close(active_cs, active_pcb); diff --git a/soc/runtime/session.c b/soc/runtime/session.c index bc4ad4545..9eb909573 100644 --- a/soc/runtime/session.c +++ b/soc/runtime/session.c @@ -18,43 +18,230 @@ #define BUFFER_IN_SIZE (1024*1024) #define BUFFER_OUT_SIZE (1024*1024) -static int buffer_in_index; -/* The 9th byte (right after the header) of buffer_in must be aligned - * to a 32-bit boundary for elf_loader to work. - */ +static int process_input(); +static int out_packet_available(); + +// ============================= Reader interface ============================= + +// Align the 9th byte (right after the header) of buffer_in so that +// the payload can be deserialized directly from the buffer using word reads. static struct { char padding[3]; - char data[BUFFER_IN_SIZE]; -} __attribute__((packed)) _buffer_in __attribute__((aligned(4))); -#define buffer_in _buffer_in.data -static int buffer_out_index_data; -static int buffer_out_index_mem; -static char buffer_out[BUFFER_OUT_SIZE]; + union { + char data[BUFFER_IN_SIZE]; + struct { + int32_t sync; + int32_t length; + int8_t type; + } __attribute__((packed)) header; + }; +} __attribute__((packed, aligned(4))) buffer_in; -static int get_in_packet_len(void) -{ - int r; +static int buffer_in_write_cursor, buffer_in_read_cursor; - memcpy(&r, &buffer_in[4], 4); - return r; +static void in_packet_reset() { + buffer_in_write_cursor = 0; + buffer_in_read_cursor = 0; } -static int get_out_packet_len(void) +static int in_packet_fill(uint8_t *data, int length) { - int r; + int consumed = 0; + while(consumed < length) { + /* Make sure the output buffer is available for any reply + * we might need to send. */ + if(!out_packet_available()) + break; - memcpy(&r, &buffer_out[4], 4); - return r; + if(buffer_in_write_cursor < 4) { + /* Haven't received the synchronization sequence yet. */ + buffer_in.data[buffer_in_write_cursor++] = data[consumed]; + + /* Framing error? */ + if(data[consumed++] != 0x5a) { + buffer_in_write_cursor = 0; + continue; + } + } else if(buffer_in_write_cursor < 8) { + /* Haven't received the packet length yet. */ + buffer_in.data[buffer_in_write_cursor++] = data[consumed++]; + } else if(buffer_in.header.length == 0) { + /* Zero-length packet means session reset. */ + return -2; + } else if(buffer_in.header.length > BUFFER_IN_SIZE) { + /* Packet wouldn't fit in the buffer. */ + return -1; + } else if(buffer_in.header.length > buffer_in_write_cursor) { + /* Receiving payload. */ + int remaining = buffer_in.header.length - buffer_in_write_cursor; + int amount = length - consumed > remaining ? remaining : length - consumed; + memcpy(&buffer_in.data[buffer_in_write_cursor], &data[consumed], + amount); + buffer_in_write_cursor += amount; + consumed += amount; + } + + if(buffer_in.header.length == buffer_in_write_cursor) { + /* We have a complete packet. */ + + buffer_in_read_cursor = sizeof(buffer_in.header); + if(!process_input()) + return -1; + + if(buffer_in_read_cursor < buffer_in_write_cursor) { + log("session.c: read underrun (%d bytes remaining)", + buffer_in_write_cursor - buffer_in_read_cursor); + } + + in_packet_reset(); + } + } + + return consumed; } -static void submit_output(int len) -{ - memset(&buffer_out[0], 0x5a, 4); - memcpy(&buffer_out[4], &len, 4); - buffer_out_index_data = 0; - buffer_out_index_mem = 0; +static void in_packet_chunk(void *ptr, int length) { + if(buffer_in_read_cursor + length > buffer_in_write_cursor) { + log("session.c: read overrun while trying to read %d bytes" + " (%d remaining)", + length, buffer_in_write_cursor - buffer_in_read_cursor); + } + + if(ptr != NULL) + memcpy(ptr, &buffer_in.data[buffer_in_read_cursor], length); + buffer_in_read_cursor += length; } +static int8_t in_packet_int8() { + int8_t result; + in_packet_chunk(&result, sizeof(result)); + return result; +} + +static int32_t in_packet_int32() { + int32_t result; + in_packet_chunk(&result, sizeof(result)); + return result; +} + +static const void *in_packet_bytes(int *length) { + *length = in_packet_int32(); + const void *ptr = &buffer_in.data[buffer_in_read_cursor]; + in_packet_chunk(NULL, *length); + return ptr; +} + +static const char *in_packet_string() { + int length; + const char *string = in_packet_bytes(&length); + if(string[length] != 0) { + log("session.c: string is not zero-terminated"); + return ""; + } + return string; +} + +// ============================= Writer interface ============================= + +static union { + char data[BUFFER_OUT_SIZE]; + struct { + int32_t sync; + int32_t length; + int8_t type; + } __attribute__((packed)) header; +} buffer_out; + +static int buffer_out_read_cursor, buffer_out_write_cursor; + +static void out_packet_reset() { + buffer_out_read_cursor = 0; + buffer_out_write_cursor = 0; +} + +static int out_packet_available() { + return buffer_out_write_cursor == 0; +} + +static void out_packet_extract(void **data, int *length) { + if(buffer_out_write_cursor > 0 && + buffer_out.header.length > 0) { + *data = &buffer_out.data[buffer_out_read_cursor]; + *length = buffer_out_write_cursor - buffer_out_read_cursor; + } else { + *length = 0; + } +} + +static void out_packet_advance(int length) { + if(buffer_out_read_cursor + length > buffer_out_write_cursor) { + log("session.c: write underrun while trying to acknowledge %d bytes" + " (%d remaining)", + length, buffer_out_write_cursor - buffer_out_read_cursor); + return; + } + + buffer_out_read_cursor += length; + if(buffer_out_read_cursor == buffer_out_write_cursor) + out_packet_reset(); +} + +static int out_packet_chunk(const void *ptr, int length) { + if(buffer_out_write_cursor + length > BUFFER_OUT_SIZE) { + log("session.c: write overrun while trying to write %d bytes" + " (%d remaining)", + length, BUFFER_OUT_SIZE - buffer_out_write_cursor); + return 0; + } + + memcpy(&buffer_out.data[buffer_out_write_cursor], ptr, length); + buffer_out_write_cursor += length; + return 1; +} + +static void out_packet_start(int type) { + buffer_out.header.sync = 0x5a5a5a5a; + buffer_out.header.type = type; + buffer_out.header.length = 0; + buffer_out_write_cursor = sizeof(buffer_out.header); +} + +static void out_packet_finish() { + buffer_out.header.length = buffer_out_write_cursor; +} + +static void out_packet_empty(int type) { + out_packet_start(type); + out_packet_finish(); +} + +static int out_packet_int8(int8_t value) { + return out_packet_chunk(&value, sizeof(value)); +} + +static int out_packet_int32(int32_t value) { + return out_packet_chunk(&value, sizeof(value)); +} + +static int out_packet_int64(int64_t value) { + return out_packet_chunk(&value, sizeof(value)); +} + +static int out_packet_float64(double value) { + return out_packet_chunk(&value, sizeof(value)); +} + +static int out_packet_bytes(const void *ptr, int length) { + return out_packet_int32(length) && + out_packet_chunk(ptr, length); +} + +static int out_packet_string(const char *string) { + return out_packet_bytes(string, strlen(string) + 1); +} + +// =============================== API handling =============================== + static int user_kernel_state; enum { @@ -66,11 +253,12 @@ enum { void session_start(void) { - buffer_in_index = 0; - memset(&buffer_out[4], 0, 4); + in_packet_reset(); + out_packet_reset(); + kloader_stop(); - user_kernel_state = USER_KERNEL_NONE; now = -1; + user_kernel_state = USER_KERNEL_NONE; } void session_end(void) @@ -118,69 +306,108 @@ enum { REMOTEMSG_TYPE_FLASH_ERROR_REPLY }; -static int check_flash_storage_key_len(char *key, unsigned int key_len) -{ - if(key_len == get_in_packet_len() - 8) { - log("Invalid key: not a null-terminated string"); - buffer_out[8] = REMOTEMSG_TYPE_FLASH_ERROR_REPLY; - submit_output(9); - return 0; - } - return 1; -} - static int process_input(void) { - switch(buffer_in[8]) { + switch(buffer_in.header.type) { + case REMOTEMSG_TYPE_IDENT_REQUEST: + out_packet_start(REMOTEMSG_TYPE_IDENT_REPLY); + out_packet_chunk("AROR", 4); + out_packet_finish(); + break; + + case REMOTEMSG_TYPE_SWITCH_CLOCK: { + int clk = in_packet_int8(); + + if(user_kernel_state >= USER_KERNEL_RUNNING) { + log("Attempted to switch RTIO clock while kernel running"); + out_packet_empty(REMOTEMSG_TYPE_CLOCK_SWITCH_FAILED); + break; + } + + if(rtiocrg_switch_clock(clk)) + out_packet_empty(REMOTEMSG_TYPE_CLOCK_SWITCH_COMPLETED); + else + out_packet_empty(REMOTEMSG_TYPE_CLOCK_SWITCH_FAILED); + break; + } + case REMOTEMSG_TYPE_LOG_REQUEST: #if (LOG_BUFFER_SIZE + 9) > BUFFER_OUT_SIZE #error Output buffer cannot hold the log buffer #endif - buffer_out[8] = REMOTEMSG_TYPE_LOG_REPLY; - log_get(&buffer_out[9]); - submit_output(9 + LOG_BUFFER_SIZE); + out_packet_start(REMOTEMSG_TYPE_LOG_REPLY); + log_get(&buffer_out.data[buffer_out_write_cursor]); + buffer_out_write_cursor += LOG_BUFFER_SIZE; + out_packet_finish(); break; - case REMOTEMSG_TYPE_IDENT_REQUEST: - buffer_out[8] = REMOTEMSG_TYPE_IDENT_REPLY; - buffer_out[9] = 'A'; - buffer_out[10] = 'R'; - buffer_out[11] = 'O'; - buffer_out[12] = 'R'; - submit_output(13); + + case REMOTEMSG_TYPE_FLASH_READ_REQUEST: { +#if SPIFLASH_SECTOR_SIZE - 4 > BUFFER_OUT_SIZE - 9 +#error Output buffer cannot hold the flash storage data +#endif + const char *key = in_packet_string(); + int value_length; + + out_packet_start(REMOTEMSG_TYPE_FLASH_READ_REPLY); + value_length = fs_read(key, &buffer_out.data[buffer_out_write_cursor], + sizeof(buffer_out.data) - buffer_out_write_cursor, NULL); + buffer_out_write_cursor += value_length; + out_packet_finish(); break; - case REMOTEMSG_TYPE_SWITCH_CLOCK: - if(user_kernel_state >= USER_KERNEL_RUNNING) { - log("Attempted to switch RTIO clock while kernel running"); - buffer_out[8] = REMOTEMSG_TYPE_CLOCK_SWITCH_FAILED; - submit_output(9); - break; - } - if(rtiocrg_switch_clock(buffer_in[9])) - buffer_out[8] = REMOTEMSG_TYPE_CLOCK_SWITCH_COMPLETED; + } + + case REMOTEMSG_TYPE_FLASH_WRITE_REQUEST: { +#if SPIFLASH_SECTOR_SIZE - 4 > BUFFER_IN_SIZE - 9 +#error Input buffer cannot hold the flash storage data +#endif + const char *key, *value; + int value_length; + key = in_packet_string(); + value = in_packet_bytes(&value_length); + + if(fs_write(key, value, value_length)) + out_packet_empty(REMOTEMSG_TYPE_FLASH_OK_REPLY); else - buffer_out[8] = REMOTEMSG_TYPE_CLOCK_SWITCH_FAILED; - submit_output(9); + out_packet_empty(REMOTEMSG_TYPE_FLASH_ERROR_REPLY); break; - case REMOTEMSG_TYPE_LOAD_LIBRARY: + } + + case REMOTEMSG_TYPE_FLASH_ERASE_REQUEST: + fs_erase(); + out_packet_empty(REMOTEMSG_TYPE_FLASH_OK_REPLY); + break; + + case REMOTEMSG_TYPE_FLASH_REMOVE_REQUEST: { + const char *key = in_packet_string(); + + fs_remove(key); + out_packet_empty(REMOTEMSG_TYPE_FLASH_OK_REPLY); + break; + } + + case REMOTEMSG_TYPE_LOAD_LIBRARY: { + const void *kernel = &buffer_in.data[buffer_in_read_cursor]; + buffer_in_read_cursor = buffer_in_write_cursor; + if(user_kernel_state >= USER_KERNEL_RUNNING) { log("Attempted to load new kernel library while already running"); - buffer_out[8] = REMOTEMSG_TYPE_LOAD_FAILED; - submit_output(9); + out_packet_empty(REMOTEMSG_TYPE_LOAD_FAILED); break; } - if(kloader_load_library(&buffer_in[9])) { - buffer_out[8] = REMOTEMSG_TYPE_LOAD_COMPLETED; + + if(kloader_load_library(kernel)) { + out_packet_empty(REMOTEMSG_TYPE_LOAD_COMPLETED); user_kernel_state = USER_KERNEL_LOADED; } else { - buffer_out[8] = REMOTEMSG_TYPE_LOAD_FAILED; + out_packet_empty(REMOTEMSG_TYPE_LOAD_FAILED); } - submit_output(9); break; - case REMOTEMSG_TYPE_RUN_KERNEL: { + } + + case REMOTEMSG_TYPE_RUN_KERNEL: if(user_kernel_state != USER_KERNEL_LOADED) { log("Attempted to run kernel while not in the LOADED state"); - buffer_out[8] = REMOTEMSG_TYPE_KERNEL_STARTUP_FAILED; - submit_output(9); + out_packet_empty(REMOTEMSG_TYPE_KERNEL_STARTUP_FAILED); break; } @@ -189,254 +416,100 @@ static int process_input(void) user_kernel_state = USER_KERNEL_RUNNING; break; - } + case REMOTEMSG_TYPE_RPC_REPLY: { struct msg_rpc_reply reply; if(user_kernel_state != USER_KERNEL_WAIT_RPC) { log("Unsolicited RPC reply"); - return 0; + return 0; // restart session } reply.type = MESSAGE_TYPE_RPC_REPLY; // FIXME memcpy(&reply.eid, &buffer_in[9], 4); - memcpy(&reply.retval, &buffer_in[13], 4); + // memcpy(&reply.retval, &buffer_in[13], 4); mailbox_send_and_wait(&reply); user_kernel_state = USER_KERNEL_RUNNING; break; } - case REMOTEMSG_TYPE_FLASH_READ_REQUEST: { -#if SPIFLASH_SECTOR_SIZE - 4 > BUFFER_OUT_SIZE - 9 -#error Output buffer cannot hold the flash storage data -#elif SPIFLASH_SECTOR_SIZE - 4 > BUFFER_IN_SIZE - 9 -#error Input buffer cannot hold the flash storage data -#endif - unsigned int ret, in_packet_len; - char *key; - in_packet_len = get_in_packet_len(); - key = &buffer_in[9]; - buffer_in[in_packet_len] = '\0'; - - buffer_out[8] = REMOTEMSG_TYPE_FLASH_READ_REPLY; - ret = fs_read(key, &buffer_out[9], sizeof(buffer_out) - 9, NULL); - submit_output(9 + ret); - break; - } - case REMOTEMSG_TYPE_FLASH_WRITE_REQUEST: { - char *key, *value; - unsigned int key_len, value_len, in_packet_len; - int ret; - - in_packet_len = get_in_packet_len(); - key = &buffer_in[9]; - key_len = strnlen(key, in_packet_len - 9) + 1; - if(!check_flash_storage_key_len(key, key_len)) - break; - - value_len = in_packet_len - key_len - 9; - value = key + key_len; - ret = fs_write(key, value, value_len); - - if(ret) - buffer_out[8] = REMOTEMSG_TYPE_FLASH_OK_REPLY; - else - buffer_out[8] = REMOTEMSG_TYPE_FLASH_ERROR_REPLY; - submit_output(9); - break; - } - case REMOTEMSG_TYPE_FLASH_ERASE_REQUEST: { - fs_erase(); - buffer_out[8] = REMOTEMSG_TYPE_FLASH_OK_REPLY; - submit_output(9); - break; - } - case REMOTEMSG_TYPE_FLASH_REMOVE_REQUEST: { - char *key; - unsigned int in_packet_len; - - in_packet_len = get_in_packet_len(); - key = &buffer_in[9]; - buffer_in[in_packet_len] = '\0'; - - fs_remove(key); - buffer_out[8] = REMOTEMSG_TYPE_FLASH_OK_REPLY; - submit_output(9); - break; - } default: return 0; } + return 1; } -/* Returns -1 in case of irrecoverable error - * (the session must be dropped and session_end called) - */ -int session_input(void *data, int len) -{ - unsigned char *_data = data; - int consumed; +static int send_rpc_value(const char **tag, void *value) { + out_packet_int8(**tag); - consumed = 0; - while(len > 0) { - /* Make sure the output buffer is available for any reply - * we might need to send. */ - if(get_out_packet_len() != 0) - return consumed; + int size = 0; + switch(**tag) { + case 0: // last tag + case 'n': // None + break; - if(buffer_in_index < 4) { - /* synchronizing */ - if(_data[consumed] == 0x5a) - buffer_in[buffer_in_index++] = 0x5a; - else - buffer_in_index = 0; - consumed++; len--; - } else if(buffer_in_index < 8) { - /* receiving length */ - buffer_in[buffer_in_index++] = _data[consumed]; - consumed++; len--; - if((buffer_in_index == 8) && (get_in_packet_len() == 0)) - /* zero-length packet = session reset */ - return -2; - } else { - /* receiving payload */ - int packet_len; - int count; + case 'b': // bool + size = 1; + out_packet_chunk(value, size); + break; - packet_len = get_in_packet_len(); - if(packet_len > BUFFER_IN_SIZE) - return -1; - count = packet_len - buffer_in_index; - if(count > len) - count = len; - memcpy(&buffer_in[buffer_in_index], &_data[consumed], count); - buffer_in_index += count; + case 'i': // int(width=32) + size = 4; + out_packet_chunk(value, size); + break; - if(buffer_in_index == packet_len) { - if(!process_input()) + case 'I': // int(width=64) + case 'f': // float + size = 8; + out_packet_chunk(value, size); + break; + + case 'F': // Fraction + size = 16; + out_packet_chunk(value, size); + break; + + case 'l': { // list(elt='a) + size = sizeof(void*); + + struct { uint32_t length; void *elements; } *list = value; + void *element = list->elements; + + const char *tag_copy = *tag + 1; + for(int i = 0; i < list->length; i++) { + int element_size = send_rpc_value(&tag_copy, element); + if(element_size < 0) return -1; - buffer_in_index = 0; + element = (void*)((intptr_t)element + element_size); } - - consumed += count; len -= count; + *tag = tag_copy; + break; } - } - return consumed; -} -static int add_base_rpc_value(char base_type, void *value, char *buffer_out, int available_space) -{ - switch(base_type) { - case 'n': - return 0; - case 'b': - if(available_space < 1) - return -1; - if(*(char *)value) - buffer_out[0] = 1; - else - buffer_out[0] = 0; - return 1; - case 'i': - if(available_space < 4) - return -1; - memcpy(buffer_out, value, 4); - return 4; - case 'I': - case 'f': - if(available_space < 8) - return -1; - memcpy(buffer_out, value, 8); - return 8; - case 'F': - if(available_space < 16) - return -1; - memcpy(buffer_out, value, 16); - return 16; default: return -1; } + + (*tag)++; + return size; } -static int add_rpc_value(int bi, int type_tag, void *value) +static int send_rpc_request(int service, va_list args) { - char base_type; - int obi, r; + out_packet_start(REMOTEMSG_TYPE_RPC_REQUEST); + out_packet_int32(service); - obi = bi; - base_type = type_tag; - - if((bi + 1) > BUFFER_OUT_SIZE) - return -1; - buffer_out[bi++] = base_type; - - if(base_type == 'l') { - char elt_type; - int len; - int i, p; - - elt_type = type_tag >> 8; - if((bi + 1) > BUFFER_OUT_SIZE) - return -1; - buffer_out[bi++] = elt_type; - - len = *(int *)value; - if((bi + 4) > BUFFER_OUT_SIZE) - return -1; - memcpy(&buffer_out[bi], &len, 4); - bi += 4; - - p = 4; - for(i=0;i BUFFER_OUT_SIZE) - return 0; - buffer_out[bi++] = 0; - submit_output(bi); + out_packet_finish(); return 1; } @@ -454,26 +527,27 @@ static int process_kmsg(struct msg_base *umsg) switch(umsg->type) { case MESSAGE_TYPE_FINISHED: - buffer_out[8] = REMOTEMSG_TYPE_KERNEL_FINISHED; - submit_output(9); + out_packet_empty(REMOTEMSG_TYPE_KERNEL_FINISHED); kloader_stop(); user_kernel_state = USER_KERNEL_LOADED; mailbox_acknowledge(); break; + case MESSAGE_TYPE_EXCEPTION: { struct msg_exception *msg = (struct msg_exception *)umsg; - buffer_out[8] = REMOTEMSG_TYPE_KERNEL_EXCEPTION; + out_packet_empty(REMOTEMSG_TYPE_KERNEL_EXCEPTION); // memcpy(&buffer_out[9], &msg->eid, 4); // memcpy(&buffer_out[13], msg->eparams, 3*8); - submit_output(9+4+3*8); + // submit_output(9+4+3*8); kloader_stop(); user_kernel_state = USER_KERNEL_LOADED; mailbox_acknowledge(); break; } + case MESSAGE_TYPE_WATCHDOG_SET_REQUEST: { struct msg_watchdog_set_request *msg = (struct msg_watchdog_set_request *)umsg; struct msg_watchdog_set_reply reply; @@ -483,6 +557,7 @@ static int process_kmsg(struct msg_base *umsg) mailbox_send_and_wait(&reply); break; } + case MESSAGE_TYPE_WATCHDOG_CLEAR: { struct msg_watchdog_clear *msg = (struct msg_watchdog_clear *)umsg; @@ -490,76 +565,72 @@ static int process_kmsg(struct msg_base *umsg) mailbox_acknowledge(); break; } + case MESSAGE_TYPE_RPC_REQUEST: { struct msg_rpc_request *msg = (struct msg_rpc_request *)umsg; - if(!send_rpc_request(msg->rpc_num, msg->args)) + if(!send_rpc_request(msg->rpc_num, msg->args)) { + log("Failed to send RPC request"); return 0; + } + user_kernel_state = USER_KERNEL_WAIT_RPC; mailbox_acknowledge(); break; } - default: { - log("Received invalid message type from kernel CPU"); + + default: + log("Received invalid message type %d from kernel CPU", + umsg->type); return 0; - } } return 1; } -/* len is set to -1 in case of irrecoverable error +/* Returns amount of bytes consumed on success. + * Returns -1 in case of irrecoverable error + * (the session must be dropped and session_end called). + * Returns -2 if the host has requested session reset. + */ +int session_input(void *data, int length) { + return in_packet_fill((uint8_t*)data, length); +} + +/* *length is set to -1 in case of irrecoverable error * (the session must be dropped and session_end called) */ -void session_poll(void **data, int *len) +void session_poll(void **data, int *length) { - int l; - if(user_kernel_state == USER_KERNEL_RUNNING) { if(watchdog_expired()) { log("Watchdog expired"); - *len = -1; + *length = -1; return; } if(!rtiocrg_check()) { log("RTIO clock failure"); - *len = -1; + *length = -1; return; } } - l = get_out_packet_len(); - /* If the output buffer is available, * check if the kernel CPU has something to transmit. */ - if(l == 0) { - struct msg_base *umsg; - - umsg = mailbox_receive(); + if(out_packet_available()) { + struct msg_base *umsg = mailbox_receive(); if(umsg) { if(!process_kmsg(umsg)) { - *len = -1; + *length = -1; return; } } - l = get_out_packet_len(); } - if(l > 0) { - *len = l - buffer_out_index_data; - *data = &buffer_out[buffer_out_index_data]; - } else - *len = 0; + out_packet_extract(data, length); } -void session_ack_data(int len) +void session_ack(int length) { - buffer_out_index_data += len; -} - -void session_ack_mem(int len) -{ - buffer_out_index_mem += len; - if(buffer_out_index_mem >= get_out_packet_len()) - memset(&buffer_out[4], 0, 4); + out_packet_advance(length); } diff --git a/soc/runtime/session.h b/soc/runtime/session.h index 988d0f6b0..8ef353b9e 100644 --- a/soc/runtime/session.h +++ b/soc/runtime/session.h @@ -4,11 +4,8 @@ void session_start(void); void session_end(void); -int session_input(void *data, int len); -void session_poll(void **data, int *len); -void session_ack_data(int len); -void session_ack_mem(int len); - -int rpc(int rpc_num, ...); +int session_input(void *data, int length); +void session_poll(void **data, int *length); +void session_ack(int length); #endif /* __SESSION_H */ From 4efae2b67d2eea9e72b343d1be8b7519181dc6c7 Mon Sep 17 00:00:00 2001 From: whitequark Date: Sat, 8 Aug 2015 13:48:25 +0300 Subject: [PATCH 186/369] Formatting. --- artiq/frontend/artiq_coretool.py | 2 +- soc/runtime/session.c | 66 +++++++++++++++++++++----------- 2 files changed, 45 insertions(+), 23 deletions(-) diff --git a/artiq/frontend/artiq_coretool.py b/artiq/frontend/artiq_coretool.py index 8da310b3e..10bed76a9 100755 --- a/artiq/frontend/artiq_coretool.py +++ b/artiq/frontend/artiq_coretool.py @@ -73,7 +73,7 @@ def main(): print(value) elif args.action == "cfg-write": for key, value in args.string: - comm.flash_storage_write(key, value.encode('utf-8')) + comm.flash_storage_write(key, value.encode("utf-8")) for key, filename in args.file: with open(filename, "rb") as fi: comm.flash_storage_write(key, fi.read()) diff --git a/soc/runtime/session.c b/soc/runtime/session.c index 9eb909573..4c394f272 100644 --- a/soc/runtime/session.c +++ b/soc/runtime/session.c @@ -39,7 +39,8 @@ static struct { static int buffer_in_write_cursor, buffer_in_read_cursor; -static void in_packet_reset() { +static void in_packet_reset() +{ buffer_in_write_cursor = 0; buffer_in_read_cursor = 0; } @@ -100,7 +101,8 @@ static int in_packet_fill(uint8_t *data, int length) return consumed; } -static void in_packet_chunk(void *ptr, int length) { +static void in_packet_chunk(void *ptr, int length) +{ if(buffer_in_read_cursor + length > buffer_in_write_cursor) { log("session.c: read overrun while trying to read %d bytes" " (%d remaining)", @@ -112,26 +114,30 @@ static void in_packet_chunk(void *ptr, int length) { buffer_in_read_cursor += length; } -static int8_t in_packet_int8() { +static int8_t in_packet_int8() +{ int8_t result; in_packet_chunk(&result, sizeof(result)); return result; } -static int32_t in_packet_int32() { +static int32_t in_packet_int32() +{ int32_t result; in_packet_chunk(&result, sizeof(result)); return result; } -static const void *in_packet_bytes(int *length) { +static const void *in_packet_bytes(int *length) +{ *length = in_packet_int32(); const void *ptr = &buffer_in.data[buffer_in_read_cursor]; in_packet_chunk(NULL, *length); return ptr; } -static const char *in_packet_string() { +static const char *in_packet_string() +{ int length; const char *string = in_packet_bytes(&length); if(string[length] != 0) { @@ -154,16 +160,19 @@ static union { static int buffer_out_read_cursor, buffer_out_write_cursor; -static void out_packet_reset() { +static void out_packet_reset() +{ buffer_out_read_cursor = 0; buffer_out_write_cursor = 0; } -static int out_packet_available() { +static int out_packet_available() +{ return buffer_out_write_cursor == 0; } -static void out_packet_extract(void **data, int *length) { +static void out_packet_extract(void **data, int *length) +{ if(buffer_out_write_cursor > 0 && buffer_out.header.length > 0) { *data = &buffer_out.data[buffer_out_read_cursor]; @@ -173,7 +182,8 @@ static void out_packet_extract(void **data, int *length) { } } -static void out_packet_advance(int length) { +static void out_packet_advance(int length) +{ if(buffer_out_read_cursor + length > buffer_out_write_cursor) { log("session.c: write underrun while trying to acknowledge %d bytes" " (%d remaining)", @@ -186,7 +196,8 @@ static void out_packet_advance(int length) { out_packet_reset(); } -static int out_packet_chunk(const void *ptr, int length) { +static int out_packet_chunk(const void *ptr, int length) +{ if(buffer_out_write_cursor + length > BUFFER_OUT_SIZE) { log("session.c: write overrun while trying to write %d bytes" " (%d remaining)", @@ -199,44 +210,53 @@ static int out_packet_chunk(const void *ptr, int length) { return 1; } -static void out_packet_start(int type) { +static void out_packet_start(int type) +{ buffer_out.header.sync = 0x5a5a5a5a; buffer_out.header.type = type; buffer_out.header.length = 0; buffer_out_write_cursor = sizeof(buffer_out.header); } -static void out_packet_finish() { +static void out_packet_finish() +{ buffer_out.header.length = buffer_out_write_cursor; } -static void out_packet_empty(int type) { +static void out_packet_empty(int type) +{ out_packet_start(type); out_packet_finish(); } -static int out_packet_int8(int8_t value) { +static int out_packet_int8(int8_t value) +{ return out_packet_chunk(&value, sizeof(value)); } -static int out_packet_int32(int32_t value) { +static int out_packet_int32(int32_t value) +{ return out_packet_chunk(&value, sizeof(value)); } -static int out_packet_int64(int64_t value) { +static int out_packet_int64(int64_t value) +{ return out_packet_chunk(&value, sizeof(value)); } -static int out_packet_float64(double value) { +static int out_packet_float64(double value) +{ return out_packet_chunk(&value, sizeof(value)); } -static int out_packet_bytes(const void *ptr, int length) { +static int out_packet_bytes(const void *ptr, int length) +{ return out_packet_int32(length) && out_packet_chunk(ptr, length); } -static int out_packet_string(const char *string) { +static int out_packet_string(const char *string) +{ return out_packet_bytes(string, strlen(string) + 1); } @@ -440,7 +460,8 @@ static int process_input(void) return 1; } -static int send_rpc_value(const char **tag, void *value) { +static int send_rpc_value(const char **tag, void *value) +{ out_packet_int8(**tag); int size = 0; @@ -592,7 +613,8 @@ static int process_kmsg(struct msg_base *umsg) * (the session must be dropped and session_end called). * Returns -2 if the host has requested session reset. */ -int session_input(void *data, int length) { +int session_input(void *data, int length) +{ return in_packet_fill((uint8_t*)data, length); } From f5ea202e25562e094cef2a6554ed164c2a408063 Mon Sep 17 00:00:00 2001 From: whitequark Date: Sat, 8 Aug 2015 14:06:11 +0300 Subject: [PATCH 187/369] session.c: bring back session_ack_{consumed,sent}. These (called session_ack_{data,mem} before) were removed in error. --- soc/runtime/main.c | 3 ++- soc/runtime/net_server.c | 3 ++- soc/runtime/session.c | 31 ++++++++++++++++++++++++------- soc/runtime/session.h | 3 ++- 4 files changed, 30 insertions(+), 10 deletions(-) diff --git a/soc/runtime/main.c b/soc/runtime/main.c index e2c3691f5..a74162891 100644 --- a/soc/runtime/main.c +++ b/soc/runtime/main.c @@ -191,7 +191,8 @@ static void serial_service(void) if(txlen > 0) { for(i = 0; i < txlen; i++) uart_write(txdata[i]); - session_ack(txlen); + session_ack_consumed(txlen); + session_ack_sent(txlen); } else if(txlen < 0) { reset_serial_session(1); } diff --git a/soc/runtime/net_server.c b/soc/runtime/net_server.c index a18e4da85..184d9fca5 100644 --- a/soc/runtime/net_server.c +++ b/soc/runtime/net_server.c @@ -89,7 +89,7 @@ static err_t net_server_recv(void *arg, struct tcp_pcb *pcb, struct pbuf *p, err static err_t net_server_sent(void *arg, struct tcp_pcb *pcb, u16_t len) { - session_ack(len); + session_ack_sent(len); return ERR_OK; } @@ -205,6 +205,7 @@ void net_server_service(void) if(len > sndbuf) len = sndbuf; tcp_write(active_pcb, data, len, 0); + session_ack_consumed(len); } if(len < 0) net_server_close(active_cs, active_pcb); diff --git a/soc/runtime/session.c b/soc/runtime/session.c index 4c394f272..eea96fee5 100644 --- a/soc/runtime/session.c +++ b/soc/runtime/session.c @@ -158,12 +158,13 @@ static union { } __attribute__((packed)) header; } buffer_out; -static int buffer_out_read_cursor, buffer_out_write_cursor; +static int buffer_out_read_cursor, buffer_out_sent_cursor, buffer_out_write_cursor; static void out_packet_reset() { buffer_out_read_cursor = 0; buffer_out_write_cursor = 0; + buffer_out_sent_cursor = 0; } static int out_packet_available() @@ -182,17 +183,28 @@ static void out_packet_extract(void **data, int *length) } } -static void out_packet_advance(int length) +static void out_packet_advance_consumed(int length) { if(buffer_out_read_cursor + length > buffer_out_write_cursor) { - log("session.c: write underrun while trying to acknowledge %d bytes" - " (%d remaining)", + log("session.c: write underrun (consume) while trying to" + " acknowledge %d bytes (%d remaining)", length, buffer_out_write_cursor - buffer_out_read_cursor); return; } buffer_out_read_cursor += length; - if(buffer_out_read_cursor == buffer_out_write_cursor) +} + +static void out_packet_advance_sent(int length) { + if(buffer_out_sent_cursor + length > buffer_out_write_cursor) { + log("session.c: write underrun (send) while trying to" + " acknowledge %d bytes (%d remaining)", + length, buffer_out_write_cursor - buffer_out_sent_cursor); + return; + } + + buffer_out_sent_cursor += length; + if(buffer_out_sent_cursor == buffer_out_write_cursor) out_packet_reset(); } @@ -652,7 +664,12 @@ void session_poll(void **data, int *length) out_packet_extract(data, length); } -void session_ack(int length) +void session_ack_consumed(int length) { - out_packet_advance(length); + out_packet_advance_consumed(length); +} + +void session_ack_sent(int length) +{ + out_packet_advance_sent(length); } diff --git a/soc/runtime/session.h b/soc/runtime/session.h index 8ef353b9e..cd0e3d76e 100644 --- a/soc/runtime/session.h +++ b/soc/runtime/session.h @@ -6,6 +6,7 @@ void session_end(void); int session_input(void *data, int length); void session_poll(void **data, int *length); -void session_ack(int length); +void session_ack_consumed(int length); +void session_ack_sent(int length); #endif /* __SESSION_H */ From 1d61e446cbc84b41feac27d5e08e52a9c8af6644 Mon Sep 17 00:00:00 2001 From: whitequark Date: Sat, 8 Aug 2015 14:12:28 +0300 Subject: [PATCH 188/369] session.c: ensure session reset on out buffer overrun during RPC. --- soc/runtime/session.c | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/soc/runtime/session.c b/soc/runtime/session.c index eea96fee5..1ad70c272 100644 --- a/soc/runtime/session.c +++ b/soc/runtime/session.c @@ -474,7 +474,8 @@ static int process_input(void) static int send_rpc_value(const char **tag, void *value) { - out_packet_int8(**tag); + if(!out_packet_int8(**tag)) + return -1; int size = 0; switch(**tag) { @@ -484,23 +485,27 @@ static int send_rpc_value(const char **tag, void *value) case 'b': // bool size = 1; - out_packet_chunk(value, size); + if(!out_packet_chunk(value, size)) + return -1; break; case 'i': // int(width=32) size = 4; - out_packet_chunk(value, size); + if(!out_packet_chunk(value, size)) + return -1; break; case 'I': // int(width=64) case 'f': // float size = 8; - out_packet_chunk(value, size); + if(!out_packet_chunk(value, size)) + return -1; break; case 'F': // Fraction size = 16; - out_packet_chunk(value, size); + if(!out_packet_chunk(value, size)) + return -1; break; case 'l': { // list(elt='a) @@ -604,7 +609,7 @@ static int process_kmsg(struct msg_base *umsg) if(!send_rpc_request(msg->rpc_num, msg->args)) { log("Failed to send RPC request"); - return 0; + return 0; // restart session } user_kernel_state = USER_KERNEL_WAIT_RPC; @@ -617,6 +622,7 @@ static int process_kmsg(struct msg_base *umsg) umsg->type); return 0; } + return 1; } From 27d2390fedaea863cd201f1221bef18e800e7158 Mon Sep 17 00:00:00 2001 From: whitequark Date: Sat, 8 Aug 2015 16:01:08 +0300 Subject: [PATCH 189/369] Add zero-cost exception support to runtime and host. --- artiq/compiler/builtins.py | 1 + .../compiler/transforms/artiq_ir_generator.py | 5 +- artiq/coredevice/comm_generic.py | 118 ++++++++++++------ artiq/coredevice/exceptions.py | 8 ++ artiq/language/core.py | 27 +++- soc/runtime/artiq_personality.h | 1 + soc/runtime/kloader.c | 20 ++- soc/runtime/kloader.h | 6 +- soc/runtime/ksupport.c | 28 +++-- soc/runtime/messages.h | 9 +- soc/runtime/session.c | 82 ++++++++++-- 11 files changed, 238 insertions(+), 67 deletions(-) diff --git a/artiq/compiler/builtins.py b/artiq/compiler/builtins.py index 6b9feb9e2..86a356ef3 100644 --- a/artiq/compiler/builtins.py +++ b/artiq/compiler/builtins.py @@ -86,6 +86,7 @@ class TException(types.TMono): ("__file__", TStr()), ("__line__", TInt(types.TValue(32))), ("__col__", TInt(types.TValue(32))), + ("__func__", TStr()), ("__message__", TStr()), ("__param0__", TInt(types.TValue(64))), ("__param1__", TInt(types.TValue(64))), diff --git a/artiq/compiler/transforms/artiq_ir_generator.py b/artiq/compiler/transforms/artiq_ir_generator.py index 84bcbc6c5..8e51b8a3e 100644 --- a/artiq/compiler/transforms/artiq_ir_generator.py +++ b/artiq/compiler/transforms/artiq_ir_generator.py @@ -157,7 +157,7 @@ class ARTIQIRGenerator(algorithm.Visitor): def visit_function(self, node, is_lambda, is_internal): if is_lambda: - name = "lambda.{}.{}".format(node.loc.line(), node.loc.column()) + name = "lambda@{}:{}".format(node.loc.line(), node.loc.column()) typ = node.type.find() else: name = node.name @@ -471,9 +471,11 @@ class ARTIQIRGenerator(algorithm.Visitor): loc_file = ir.Constant(self.current_loc.source_buffer.name, builtins.TStr()) loc_line = ir.Constant(self.current_loc.line(), builtins.TInt(types.TValue(32))) loc_column = ir.Constant(self.current_loc.column(), builtins.TInt(types.TValue(32))) + 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)) @@ -1237,6 +1239,7 @@ class ARTIQIRGenerator(algorithm.Visitor): ir.Constant("", builtins.TStr()), # file ir.Constant(0, builtins.TInt(types.TValue(32))), # line ir.Constant(0, builtins.TInt(types.TValue(32))), # column + ir.Constant("", builtins.TStr()), # function ] if message is None: diff --git a/artiq/coredevice/comm_generic.py b/artiq/coredevice/comm_generic.py index b386a7430..df0cbb4cd 100644 --- a/artiq/coredevice/comm_generic.py +++ b/artiq/coredevice/comm_generic.py @@ -1,5 +1,6 @@ import struct import logging +import traceback from enum import Enum from fractions import Fraction @@ -18,11 +19,12 @@ class _H2DMsgType(Enum): RUN_KERNEL = 5 RPC_REPLY = 6 + RPC_EXCEPTION = 7 - FLASH_READ_REQUEST = 7 - FLASH_WRITE_REQUEST = 8 - FLASH_ERASE_REQUEST = 9 - FLASH_REMOVE_REQUEST = 10 + FLASH_READ_REQUEST = 8 + FLASH_WRITE_REQUEST = 9 + FLASH_ERASE_REQUEST = 10 + FLASH_REMOVE_REQUEST = 11 class _D2HMsgType(Enum): @@ -223,16 +225,12 @@ class CommGeneric: self._read_empty(_D2HMsgType.CLOCK_SWITCH_COMPLETED) - def load(self, kernel_library): - self._write_header(_H2DMsgType.LOAD_LIBRARY) - self._write_chunk(kernel_library) - self._write_flush() + def get_log(self): + self._write_empty(_H2DMsgType.LOG_REQUEST) - self._read_empty(_D2HMsgType.LOAD_COMPLETED) - - def run(self): - self._write_empty(_H2DMsgType.RUN_KERNEL) - logger.debug("running kernel") + self._read_header() + self._read_expect(_D2HMsgType.LOG_REPLY) + return self._read_chunk(self._read_length).decode('utf-8') def flash_storage_read(self, key): self._write_header(_H2DMsgType.FLASH_READ_REQUEST) @@ -267,7 +265,18 @@ class CommGeneric: self._read_empty(_D2HMsgType.FLASH_OK_REPLY) - def _receive_rpc_value(self, tag): + def load(self, kernel_library): + self._write_header(_H2DMsgType.LOAD_LIBRARY) + self._write_chunk(kernel_library) + self._write_flush() + + self._read_empty(_D2HMsgType.LOAD_COMPLETED) + + def run(self): + self._write_empty(_H2DMsgType.RUN_KERNEL) + logger.debug("running kernel") + + def _receive_rpc_value(self, tag, rpc_map): if tag == "n": return None elif tag == "b": @@ -286,37 +295,83 @@ class CommGeneric: elt_tag = chr(self._read_int8()) length = self._read_int32() return [self._receive_rpc_value(elt_tag) for _ in range(length)] + elif tag == "o": + return rpc_map[self._read_int32()] else: raise IOError("Unknown RPC value tag: {}", tag) - def _receive_rpc_values(self): + def _receive_rpc_values(self, rpc_map): result = [] while True: tag = chr(self._read_int8()) if tag == "\x00": return result else: - result.append(self._receive_rpc_value(tag)) + result.append(self._receive_rpc_value(tag, rpc_map)) def _serve_rpc(self, rpc_map): service = self._read_int32() - args = self._receive_rpc_values() + args = self._receive_rpc_values(rpc_map) logger.debug("rpc service: %d %r", service, args) - eid, result = rpc_wrapper.run_rpc(rpc_map[rpc_num], args) - logger.debug("rpc service: %d %r == %r (eid %d)", service, args, - result, eid) + try: + result = rpc_map[rpc_num](args) + if not isinstance(result, int) or not (-2**31 < result < 2**31-1): + raise ValueError("An RPC must return an int(width=32)") + except ARTIQException as exn: + logger.debug("rpc service: %d %r ! %r", service, args, exn) - self._write_header(_H2DMsgType.RPC_REPLY) - self._write_int32(eid) - self._write_int32(result) - self._write_flush() + self._write_header(_H2DMsgType.RPC_EXCEPTION) + self._write_string(exn.name) + self._write_string(exn.message) + for index in range(3): + self._write_int64(exn.param[index]) + + self._write_string(exn.filename) + self._write_int32(exn.line) + self._write_int32(exn.column) + self._write_string(exn.function) + + self._write_flush() + except Exception as exn: + logger.debug("rpc service: %d %r ! %r", service, args, exn) + + self._write_header(_H2DMsgType.RPC_EXCEPTION) + self._write_string(type(exn).__name__) + self._write_string(str(exn)) + for index in range(3): + self._write_int64(0) + + ((filename, line, function, _), ) = traceback.extract_tb(exn.__traceback__) + self._write_string(filename) + self._write_int32(line) + self._write_int32(-1) # column not known + self._write_string(function) + + self._write_flush() + else: + logger.debug("rpc service: %d %r == %r", service, args, result) + + self._write_header(_H2DMsgType.RPC_REPLY) + self._write_int32(result) + self._write_flush() def _serve_exception(self): - eid = self._read_int32() - params = [self._read_int64() for _ in range(3)] - rpc_wrapper.filter_rpc_exception(eid) - raise exception(self.core, *params) + name = self._read_string() + message = self._read_string() + params = [self._read_int64() for _ in range(3)] + + filename = self._read_string() + line = self._read_int32() + column = self._read_int32() + function = self._read_string() + + backtrace = [self._read_int32() for _ in range(self._read_int32())] + # we don't have debug information yet. + # print("exception backtrace:", [hex(x) for x in backtrace]) + + raise core_language.ARTIQException(name, message, params, + filename, line, column, function) def serve(self, rpc_map): while True: @@ -328,10 +383,3 @@ class CommGeneric: else: self._read_expect(_D2HMsgType.KERNEL_FINISHED) return - - def get_log(self): - self._write_empty(_H2DMsgType.LOG_REQUEST) - - self._read_header() - self._read_expect(_D2HMsgType.LOG_REPLY) - return self._read_chunk(self._read_length).decode('utf-8') diff --git a/artiq/coredevice/exceptions.py b/artiq/coredevice/exceptions.py index 05d475908..d0f54f2fa 100644 --- a/artiq/coredevice/exceptions.py +++ b/artiq/coredevice/exceptions.py @@ -1,5 +1,13 @@ from artiq.language.core import ARTIQException +class ZeroDivisionError(ARTIQException): + """Python's :class:`ZeroDivisionError`, mirrored in ARTIQ.""" + +class ValueError(ARTIQException): + """Python's :class:`ValueError`, mirrored in ARTIQ.""" + +class IndexError(ARTIQException): + """Python's :class:`IndexError`, mirrored in ARTIQ.""" class InternalError(ARTIQException): """Raised when the runtime encounters an internal error condition.""" diff --git a/artiq/language/core.py b/artiq/language/core.py index d8e24a552..a7dfecc5d 100644 --- a/artiq/language/core.py +++ b/artiq/language/core.py @@ -2,6 +2,7 @@ Core ARTIQ extensions to the Python language. """ +import linecache from collections import namedtuple from functools import wraps @@ -278,7 +279,8 @@ class ARTIQException(Exception): """Base class for exceptions raised or passed through the core device.""" # Try and create an instance of the specific class, if one exists. - def __new__(cls, name, message, params): + def __new__(cls, name, message, + params, filename, line, column, function): def find_subclass(cls): if cls.__name__ == name: return cls @@ -293,15 +295,30 @@ class ARTIQException(Exception): more_specific_cls = cls exn = Exception.__new__(more_specific_cls) - exn.__init__(name, message, params) + exn.__init__(name, message, params, + filename, line, column, function) return exn - def __init__(self, name, message, params): + def __init__(self, name, message, params, + filename, line, column, function): Exception.__init__(self, name, message, *params) self.name, self.message, self.params = name, message, params + self.filename, self.line, self.column = filename, line, column + self.function = function def __str__(self): + lines = [] + if type(self).__name__ == self.name: - return self.message.format(*self.params) + lines.append(self.message.format(*self.params)) else: - return "({}) {}".format(self.name, self.message.format(*self.params)) + lines.append("({}) {}".format(self.name, self.message.format(*self.params))) + + lines.append("Core Device Traceback (most recent call last):") + lines.append(" File \"{file}\", line {line}, column {column}, in {function}". + format(file=self.filename, line=self.line, column=self.column + 1, + function=self.function)) + line = linecache.getline(self.filename, self.line) + lines.append(" {}".format(line.strip() if line else "")) + + return "\n".join(lines) diff --git a/soc/runtime/artiq_personality.h b/soc/runtime/artiq_personality.h index 1952e7269..11a10b2b9 100644 --- a/soc/runtime/artiq_personality.h +++ b/soc/runtime/artiq_personality.h @@ -12,6 +12,7 @@ struct artiq_exception { const char *file; int32_t line; int32_t column; + const char *function; const char *message; int64_t param[3]; }; diff --git a/soc/runtime/kloader.c b/soc/runtime/kloader.c index 5d1cb0a32..14b4abb7b 100644 --- a/soc/runtime/kloader.c +++ b/soc/runtime/kloader.c @@ -30,7 +30,7 @@ void kloader_start_bridge() start_kernel_cpu(NULL); } -static int load_or_start_kernel(void *library, int run_kernel) +static int load_or_start_kernel(const void *library, int run_kernel) { static struct dyld_info library_info; struct msg_load_request request = { @@ -56,7 +56,7 @@ static int load_or_start_kernel(void *library, int run_kernel) return 1; } -int kloader_load_library(void *library) +int kloader_load_library(const void *library) { if(!kernel_cpu_reset_read()) { log("BUG: attempted to load kernel library while kernel CPU is running"); @@ -66,6 +66,22 @@ int kloader_load_library(void *library) return load_or_start_kernel(library, 0); } +void kloader_filter_backtrace(struct artiq_backtrace_item *backtrace, + size_t *backtrace_size) { + struct artiq_backtrace_item *cursor = backtrace; + + // Remove all backtrace items belonging to ksupport and subtract + // shared object base from the addresses. + for(int i = 0; i < *backtrace_size; i++) { + if(backtrace[i].function > KERNELCPU_PAYLOAD_ADDRESS) { + backtrace[i].function -= KERNELCPU_PAYLOAD_ADDRESS; + *cursor++ = backtrace[i]; + } + } + + *backtrace_size = cursor - backtrace; +} + void kloader_start_kernel() { load_or_start_kernel(NULL, 1); diff --git a/soc/runtime/kloader.h b/soc/runtime/kloader.h index 834fd8d3d..807617e25 100644 --- a/soc/runtime/kloader.h +++ b/soc/runtime/kloader.h @@ -1,6 +1,8 @@ #ifndef __KLOADER_H #define __KLOADER_H +#include "artiq_personality.h" + #define KERNELCPU_EXEC_ADDRESS 0x40400000 #define KERNELCPU_PAYLOAD_ADDRESS 0x40420000 #define KERNELCPU_LAST_ADDRESS (0x4fffffff - 1024*1024) @@ -8,7 +10,9 @@ extern long long int now; -int kloader_load_library(void *code); +int kloader_load_library(const void *code); +void kloader_filter_backtrace(struct artiq_backtrace_item *backtrace, + size_t *backtrace_size); void kloader_start_bridge(void); int kloader_start_idle_kernel(void); diff --git a/soc/runtime/ksupport.c b/soc/runtime/ksupport.c index da02bc4a0..b2082beef 100644 --- a/soc/runtime/ksupport.c +++ b/soc/runtime/ksupport.c @@ -246,7 +246,8 @@ long long int now_init(void) reply = mailbox_wait_and_receive(); if(reply->type != MESSAGE_TYPE_NOW_INIT_REPLY) { - log("Malformed MESSAGE_TYPE_NOW_INIT_REQUEST reply type"); + log("Malformed MESSAGE_TYPE_NOW_INIT_REQUEST reply type %d", + reply->type); while(1); } now = reply->now; @@ -281,7 +282,8 @@ int watchdog_set(int ms) reply = mailbox_wait_and_receive(); if(reply->type != MESSAGE_TYPE_WATCHDOG_SET_REPLY) { - log("Malformed MESSAGE_TYPE_WATCHDOG_SET_REQUEST reply type"); + log("Malformed MESSAGE_TYPE_WATCHDOG_SET_REQUEST reply type %d", + reply->type); while(1); } id = reply->id; @@ -302,7 +304,7 @@ void watchdog_clear(int id) int rpc(int rpc_num, ...) { struct msg_rpc_request request; - struct msg_rpc_reply *reply; + struct msg_base *reply; request.type = MESSAGE_TYPE_RPC_REQUEST; request.rpc_num = rpc_num; @@ -311,20 +313,20 @@ int rpc(int rpc_num, ...) va_end(request.args); reply = mailbox_wait_and_receive(); - if(reply->type != MESSAGE_TYPE_RPC_REPLY) { - log("Malformed MESSAGE_TYPE_RPC_REPLY reply type"); - while(1); - } - - if(reply->exception != NULL) { + if(reply->type == MESSAGE_TYPE_RPC_REPLY) { + int result = ((struct msg_rpc_reply *)reply)->result; + mailbox_acknowledge(); + return result; + } else if(reply->type == MESSAGE_TYPE_RPC_EXCEPTION) { struct artiq_exception exception; - memcpy(&exception, reply->exception, sizeof(exception)); + memcpy(&exception, ((struct msg_rpc_exception *)reply)->exception, + sizeof(struct artiq_exception)); mailbox_acknowledge(); __artiq_raise(&exception); } else { - int retval = reply->retval; - mailbox_acknowledge(); - return retval; + log("Malformed MESSAGE_TYPE_RPC_REQUEST reply type %d", + reply->type); + while(1); } } diff --git a/soc/runtime/messages.h b/soc/runtime/messages.h index cb0c9c20b..651c7a5ef 100644 --- a/soc/runtime/messages.h +++ b/soc/runtime/messages.h @@ -16,6 +16,7 @@ enum { MESSAGE_TYPE_WATCHDOG_CLEAR, MESSAGE_TYPE_RPC_REQUEST, MESSAGE_TYPE_RPC_REPLY, + MESSAGE_TYPE_RPC_EXCEPTION, MESSAGE_TYPE_LOG, MESSAGE_TYPE_BRG_READY, @@ -37,7 +38,7 @@ struct msg_base { /* kernel messages */ struct msg_load_request { - void *library; + const void *library; struct dyld_info *library_info; int run_kernel; }; @@ -86,9 +87,13 @@ struct msg_rpc_request { }; struct msg_rpc_reply { + int type; + int result; +}; + +struct msg_rpc_exception { int type; struct artiq_exception *exception; - int retval; }; struct msg_log { diff --git a/soc/runtime/session.c b/soc/runtime/session.c index 1ad70c272..b571c5873 100644 --- a/soc/runtime/session.c +++ b/soc/runtime/session.c @@ -128,6 +128,13 @@ static int32_t in_packet_int32() return result; } +static int64_t in_packet_int64() +{ + int64_t result; + in_packet_chunk(&result, sizeof(result)); + return result; +} + static const void *in_packet_bytes(int *length) { *length = in_packet_int32(); @@ -310,6 +317,7 @@ enum { REMOTEMSG_TYPE_RUN_KERNEL, REMOTEMSG_TYPE_RPC_REPLY, + REMOTEMSG_TYPE_RPC_EXCEPTION, REMOTEMSG_TYPE_FLASH_READ_REQUEST, REMOTEMSG_TYPE_FLASH_WRITE_REQUEST, @@ -452,15 +460,44 @@ static int process_input(void) case REMOTEMSG_TYPE_RPC_REPLY: { struct msg_rpc_reply reply; + int result = in_packet_int32(); + if(user_kernel_state != USER_KERNEL_WAIT_RPC) { log("Unsolicited RPC reply"); return 0; // restart session } reply.type = MESSAGE_TYPE_RPC_REPLY; - // FIXME memcpy(&reply.eid, &buffer_in[9], 4); - // memcpy(&reply.retval, &buffer_in[13], 4); + reply.result = result; mailbox_send_and_wait(&reply); + + user_kernel_state = USER_KERNEL_RUNNING; + break; + } + + case REMOTEMSG_TYPE_RPC_EXCEPTION: { + struct msg_rpc_exception reply; + + struct artiq_exception exception; + exception.name = in_packet_string(); + exception.message = in_packet_string(); + exception.param[0] = in_packet_int64(); + exception.param[1] = in_packet_int64(); + exception.param[2] = in_packet_int64(); + exception.file = in_packet_string(); + exception.line = in_packet_int32(); + exception.column = in_packet_int32(); + exception.function = in_packet_string(); + + if(user_kernel_state != USER_KERNEL_WAIT_RPC) { + log("Unsolicited RPC exception reply"); + return 0; // restart session + } + + reply.type = MESSAGE_TYPE_RPC_EXCEPTION; + reply.exception = &exception; + mailbox_send_and_wait(&reply); + user_kernel_state = USER_KERNEL_RUNNING; break; } @@ -509,8 +546,6 @@ static int send_rpc_value(const char **tag, void *value) break; case 'l': { // list(elt='a) - size = sizeof(void*); - struct { uint32_t length; void *elements; } *list = value; void *element = list->elements; @@ -522,6 +557,18 @@ static int send_rpc_value(const char **tag, void *value) element = (void*)((intptr_t)element + element_size); } *tag = tag_copy; + + size = sizeof(list); + break; + } + + case 'o': { // host object + struct { uint32_t id; } *object = value; + + if(!out_packet_int32(object->id)) + return -1; + + size = sizeof(object); break; } @@ -575,10 +622,29 @@ static int process_kmsg(struct msg_base *umsg) case MESSAGE_TYPE_EXCEPTION: { struct msg_exception *msg = (struct msg_exception *)umsg; - out_packet_empty(REMOTEMSG_TYPE_KERNEL_EXCEPTION); - // memcpy(&buffer_out[9], &msg->eid, 4); - // memcpy(&buffer_out[13], msg->eparams, 3*8); - // submit_output(9+4+3*8); + out_packet_start(REMOTEMSG_TYPE_KERNEL_EXCEPTION); + + out_packet_string(msg->exception->name); + out_packet_string(msg->exception->message); + out_packet_int64(msg->exception->param[0]); + out_packet_int64(msg->exception->param[1]); + out_packet_int64(msg->exception->param[2]); + + out_packet_string(msg->exception->file); + out_packet_int32(msg->exception->line); + out_packet_int32(msg->exception->column); + out_packet_string(msg->exception->function); + + kloader_filter_backtrace(msg->backtrace, + &msg->backtrace_size); + + out_packet_int32(msg->backtrace_size); + for(int i = 0; i < msg->backtrace_size; i++) { + struct artiq_backtrace_item *item = &msg->backtrace[i]; + out_packet_int32(item->function + item->offset); + } + + out_packet_finish(); kloader_stop(); user_kernel_state = USER_KERNEL_LOADED; From 96c770190c4329c94d48970aebe17994cd6e8416 Mon Sep 17 00:00:00 2001 From: whitequark Date: Sat, 8 Aug 2015 16:09:47 +0300 Subject: [PATCH 190/369] Add column marker to ARTIQ exception traceback. --- artiq/language/core.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/artiq/language/core.py b/artiq/language/core.py index a7dfecc5d..9ada84e9f 100644 --- a/artiq/language/core.py +++ b/artiq/language/core.py @@ -2,7 +2,7 @@ Core ARTIQ extensions to the Python language. """ -import linecache +import linecache, re from collections import namedtuple from functools import wraps @@ -320,5 +320,6 @@ class ARTIQException(Exception): function=self.function)) line = linecache.getline(self.filename, self.line) lines.append(" {}".format(line.strip() if line else "")) + lines.append(" {}^".format(" " * (self.column - re.search(r"^\s+", line).end()))) return "\n".join(lines) From acd8d6355fc7bf1e91d52b77c7552423c74ee7f7 Mon Sep 17 00:00:00 2001 From: whitequark Date: Sat, 8 Aug 2015 16:18:57 +0300 Subject: [PATCH 191/369] transforms.ARTIQIRGenerator: IndexError loc should point to "[". --- .../compiler/transforms/artiq_ir_generator.py | 25 ++++++++++++------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/artiq/compiler/transforms/artiq_ir_generator.py b/artiq/compiler/transforms/artiq_ir_generator.py index 8e51b8a3e..64d79915f 100644 --- a/artiq/compiler/transforms/artiq_ir_generator.py +++ b/artiq/compiler/transforms/artiq_ir_generator.py @@ -466,12 +466,16 @@ class ARTIQIRGenerator(algorithm.Visitor): def visit_Continue(self, node): self.append(ir.Branch(self.continue_target)) - def raise_exn(self, exn): + def raise_exn(self, exn, loc=None): if exn is not None: - loc_file = ir.Constant(self.current_loc.source_buffer.name, builtins.TStr()) - loc_line = ir.Constant(self.current_loc.line(), builtins.TInt(types.TValue(32))) - loc_column = ir.Constant(self.current_loc.column(), builtins.TInt(types.TValue(32))) + if loc is None: + loc = self.current_loc + + loc_file = ir.Constant(loc.source_buffer.name, builtins.TStr()) + loc_line = ir.Constant(loc.line(), builtins.TInt(types.TValue(32))) + loc_column = ir.Constant(loc.column(), builtins.TInt(types.TValue(32))) 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)) @@ -686,7 +690,7 @@ class ARTIQIRGenerator(algorithm.Visitor): else: self.append(ir.SetAttr(obj, node.attr, self.current_assign)) - def _map_index(self, length, index, one_past_the_end=False): + 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)) @@ -703,7 +707,7 @@ class ARTIQIRGenerator(algorithm.Visitor): exn = self.alloc_exn(builtins.TIndexError(), ir.Constant("index {0} out of bounds 0:{1}", builtins.TStr()), index, length) - self.raise_exn(exn) + self.raise_exn(exn, loc=loc) self.current_block = in_bounds_block = self.add_block() head.append(ir.BranchIf(in_bounds, in_bounds_block, out_of_bounds_block)) @@ -754,7 +758,8 @@ class ARTIQIRGenerator(algorithm.Visitor): if isinstance(node.slice, ast.Index): index = self.visit(node.slice.value) length = self.iterable_len(value, index.type) - mapped_index = self._map_index(length, index) + mapped_index = self._map_index(length, index, + loc=node.begin_loc) if self.current_assign is None: result = self.iterable_get(value, mapped_index) result.set_name("{}.at.{}".format(value.name, _readable_name(index))) @@ -769,13 +774,15 @@ class ARTIQIRGenerator(algorithm.Visitor): start_index = self.visit(node.slice.lower) else: start_index = ir.Constant(0, node.slice.type) - mapped_start_index = self._map_index(length, start_index) + mapped_start_index = self._map_index(length, start_index, + loc=node.begin_loc) if node.slice.upper is not None: stop_index = self.visit(node.slice.upper) else: stop_index = length - mapped_stop_index = self._map_index(length, stop_index, one_past_the_end=True) + mapped_stop_index = self._map_index(length, stop_index, one_past_the_end=True, + loc=node.begin_loc) if node.slice.step is not None: step = self.visit(node.slice.step) From bdcf7f100b33000c0fd46962db2eac9c5d1d640d Mon Sep 17 00:00:00 2001 From: whitequark Date: Sat, 8 Aug 2015 16:26:53 +0300 Subject: [PATCH 192/369] ARTIQIRGenerator: add semantic locs to all other implicitly raised exceptions. --- artiq/compiler/testbench/inferencer.py | 2 +- .../compiler/transforms/artiq_ir_generator.py | 19 ++++++++++++------- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/artiq/compiler/testbench/inferencer.py b/artiq/compiler/testbench/inferencer.py index d11150c43..174baf8f4 100644 --- a/artiq/compiler/testbench/inferencer.py +++ b/artiq/compiler/testbench/inferencer.py @@ -66,7 +66,7 @@ def main(): buf = source.Buffer("".join(fileinput.input()).expandtabs(), os.path.basename(fileinput.filename())) parsed, comments = parse_buffer(buf, engine=engine) - typed = ASTTypedRewriter(engine=engine, globals=prelude.globals()).visit(parsed) + typed = ASTTypedRewriter(engine=engine, prelude=prelude.globals()).visit(parsed) Inferencer(engine=engine).visit(typed) if monomorphize: IntMonomorphizer(engine=engine).visit(typed) diff --git a/artiq/compiler/transforms/artiq_ir_generator.py b/artiq/compiler/transforms/artiq_ir_generator.py index 64d79915f..2467bdc6c 100644 --- a/artiq/compiler/transforms/artiq_ir_generator.py +++ b/artiq/compiler/transforms/artiq_ir_generator.py @@ -714,13 +714,13 @@ class ARTIQIRGenerator(algorithm.Visitor): return mapped_index - def _make_check(self, cond, exn_gen): + def _make_check(self, cond, exn_gen, loc=None): # 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() - self.raise_exn(exn_gen()) + self.raise_exn(exn_gen(), loc=loc) self.current_block = tail_block = self.add_block() cond_block.append(ir.BranchIf(cond, tail_block, body_block)) @@ -789,7 +789,8 @@ class ARTIQIRGenerator(algorithm.Visitor): self._make_check( self.append(ir.Compare(ast.NotEq(loc=None), step, ir.Constant(0, step.type))), lambda: self.alloc_exn(builtins.TValueError(), - ir.Constant("step cannot be zero", builtins.TStr()))) + 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, @@ -811,7 +812,8 @@ class ARTIQIRGenerator(algorithm.Visitor): lambda: self.alloc_exn(builtins.TValueError(), ir.Constant("slice size {0} is larger than iterable length {1}", builtins.TStr()), - slice_size, length)) + slice_size, length), + loc=node.slice.loc) if self.current_assign is None: is_neg_size = self.append(ir.Compare(ast.Lt(loc=None), @@ -992,20 +994,23 @@ class ARTIQIRGenerator(algorithm.Visitor): def visit_BinOpT(self, node): if 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.TValueError(), - ir.Constant("shift amount must be nonnegative", builtins.TStr()))) + ir.Constant("shift amount must be nonnegative", builtins.TStr())), + loc=node.right.loc) elif isinstance(node.op, (ast.Div, ast.FloorDiv)): self._make_check( self.append(ir.Compare(ast.NotEq(loc=None), rhs, ir.Constant(0, rhs.type))), lambda: self.alloc_exn(builtins.TZeroDivisionError(), - ir.Constant("cannot divide by zero", builtins.TStr()))) + ir.Constant("cannot divide by zero", builtins.TStr())), + loc=node.right.loc) - return self.append(ir.Arith(node.op, self.visit(node.left), rhs)) + return self.append(ir.Arith(node.op, lhs, rhs)) 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 types.is_tuple(node.right.type): From ee3f35c608d4ab9ef09203ac875720dc7dfa71de Mon Sep 17 00:00:00 2001 From: whitequark Date: Sat, 8 Aug 2015 21:06:13 +0300 Subject: [PATCH 193/369] Improve error message on passing an argument twice. --- artiq/compiler/transforms/inferencer.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/artiq/compiler/transforms/inferencer.py b/artiq/compiler/transforms/inferencer.py index 6df5d3eec..47145b9a9 100644 --- a/artiq/compiler/transforms/inferencer.py +++ b/artiq/compiler/transforms/inferencer.py @@ -696,7 +696,7 @@ class Inferencer(algorithm.Visitor): return typ = node.func.type.find() - passed_args = set() + passed_args = dict() if len(node.args) > typ.arity(): note = diagnostic.Diagnostic("note", @@ -714,14 +714,14 @@ class Inferencer(algorithm.Visitor): zip(node.args, list(typ.args.items()) + list(typ.optargs.items())): self._unify(actualarg.type, formaltyp, actualarg.loc, None) - passed_args.add(formalname) + passed_args[formalname] = actualarg.loc for keyword in node.keywords: if keyword.arg in passed_args: diag = diagnostic.Diagnostic("error", - "the argument '{name}' is already passed", + "the argument '{name}' has been passed earlier as positional", {"name": keyword.arg}, - keyword.arg_loc) + keyword.arg_loc, [passed_args[keyword.arg]]) self.engine.process(diag) return From 13ad9b5d0898a9e2247c0e0506b7056a41394710 Mon Sep 17 00:00:00 2001 From: whitequark Date: Sat, 8 Aug 2015 21:47:20 +0300 Subject: [PATCH 194/369] Allow to dump ARTIQ/LLVM IR for stitched code. --- artiq/coredevice/core.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/artiq/coredevice/core.py b/artiq/coredevice/core.py index 7367f0529..ce9ae391b 100644 --- a/artiq/coredevice/core.py +++ b/artiq/coredevice/core.py @@ -32,10 +32,19 @@ class Core: stitcher = Stitcher(engine=engine) stitcher.stitch_call(function, args, kwargs) - module = Module(stitcher) - library = OR1KTarget().compile_and_link([module]) + module = Module(stitcher) + target = OR1KTarget() - return library, stitcher.rpc_map + if os.getenv('ARTIQ_DUMP_IR'): + print("====== ARTIQ IR DUMP ======", file=sys.stderr) + for function in module.artiq_ir: + print(function, file=sys.stderr) + + if os.getenv('ARTIQ_DUMP_LLVM'): + print("====== LLVM IR DUMP ======", file=sys.stderr) + print(module.build_llvm_ir(target), file=sys.stderr) + + return target.compile_and_link([module]), stitcher.rpc_map except diagnostic.Error as error: print("\n".join(error.diagnostic.render(colored=True)), file=sys.stderr) raise CompileError() from error From 22457bc19ca615c860b133fbd02c5ce2debd1209 Mon Sep 17 00:00:00 2001 From: whitequark Date: Sat, 8 Aug 2015 21:48:21 +0300 Subject: [PATCH 195/369] Ensure uwtable is added to all generated functions. --- artiq/compiler/transforms/llvm_ir_generator.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/artiq/compiler/transforms/llvm_ir_generator.py b/artiq/compiler/transforms/llvm_ir_generator.py index 5d5e4addb..bccc2397c 100644 --- a/artiq/compiler/transforms/llvm_ir_generator.py +++ b/artiq/compiler/transforms/llvm_ir_generator.py @@ -175,6 +175,7 @@ class LLVMIRGenerator: def process_function(self, func): try: self.llfunction = self.llmodule.get_global(func.name) + if self.llfunction is None: llargtys = [] for arg in func.arguments: @@ -182,10 +183,12 @@ class LLVMIRGenerator: llfunty = ll.FunctionType(args=llargtys, return_type=self.llty_of_type(func.type.ret, for_return=True)) self.llfunction = ll.Function(self.llmodule, llfunty, func.name) - self.llfunction.attributes.add('uwtable') + if func.is_internal: self.llfunction.linkage = 'internal' + self.llfunction.attributes.add('uwtable') + self.llmap = {} self.llbuilder = ll.IRBuilder() self.fixups = [] From b26af5df60c8e131546a9e14886aa7af78d9779b Mon Sep 17 00:00:00 2001 From: whitequark Date: Sun, 9 Aug 2015 02:17:19 +0300 Subject: [PATCH 196/369] Implement sending RPCs. --- artiq/compiler/builtins.py | 4 +- artiq/compiler/embedding.py | 125 ++++++++++-- artiq/compiler/module.py | 19 +- .../compiler/transforms/llvm_ir_generator.py | 98 +++++++++- artiq/compiler/types.py | 34 +++- artiq/coredevice/comm_generic.py | 41 ++-- artiq/coredevice/core.py | 4 +- soc/runtime/ksupport.c | 35 ++-- soc/runtime/ksupport.h | 2 +- soc/runtime/messages.h | 19 +- soc/runtime/session.c | 183 ++++++++++++------ 11 files changed, 433 insertions(+), 131 deletions(-) diff --git a/artiq/compiler/builtins.py b/artiq/compiler/builtins.py index 86a356ef3..4a8280631 100644 --- a/artiq/compiler/builtins.py +++ b/artiq/compiler/builtins.py @@ -163,7 +163,7 @@ def is_bool(typ): def is_int(typ, width=None): if width is not None: - return types.is_mono(typ, "int", {"width": width}) + return types.is_mono(typ, "int", width=width) else: return types.is_mono(typ, "int") @@ -184,7 +184,7 @@ def is_numeric(typ): def is_list(typ, elt=None): if elt is not None: - return types.is_mono(typ, "list", {"elt": elt}) + return types.is_mono(typ, "list", elt=elt) else: return types.is_mono(typ, "list") diff --git a/artiq/compiler/embedding.py b/artiq/compiler/embedding.py index 4a744131d..bf1ed49d0 100644 --- a/artiq/compiler/embedding.py +++ b/artiq/compiler/embedding.py @@ -5,10 +5,13 @@ the references to the host objects and translates the functions annotated as ``@kernel`` when they are referenced. """ -import inspect, os +import os, re, linecache, inspect +from collections import OrderedDict + from pythonparser import ast, source, diagnostic, parse_buffer + from . import types, builtins, asttyped, prelude -from .transforms import ASTTypedRewriter, Inferencer +from .transforms import ASTTypedRewriter, Inferencer, IntMonomorphizer class ASTSynthesizer: @@ -45,6 +48,9 @@ class ASTSynthesizer: typ = builtins.TFloat() return asttyped.NumT(n=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, list): begin_loc = self._add("[") elts = [] @@ -123,7 +129,7 @@ class StitchingASTTypedRewriter(ASTTypedRewriter): if inspect.isfunction(value): # It's a function. We need to translate the function and insert # a reference to it. - function_name = self.quote_function(value) + function_name = self.quote_function(value, node.loc) return asttyped.NameT(id=function_name, ctx=None, type=self.globals[function_name], loc=node.loc) @@ -154,7 +160,19 @@ class Stitcher: self.functions = {} + self.next_rpc = 0 self.rpc_map = {} + self.inverse_rpc_map = {} + + def _map(self, obj): + obj_id = id(obj) + if obj_id in self.inverse_rpc_map: + return self.inverse_rpc_map[obj_id] + + self.next_rpc += 1 + self.rpc_map[self.next_rpc] = obj + self.inverse_rpc_map[obj_id] = self.next_rpc + return self.next_rpc def _iterate(self): inferencer = Inferencer(engine=self.engine) @@ -213,17 +231,102 @@ class Stitcher: quote_function=self._quote_function) return asttyped_rewriter.visit(function_node) - def _quote_function(self, function): + def _function_def_note(self, function): + filename = function.__code__.co_filename + line = function.__code__.co_firstlineno + name = function.__code__.co_name + + source_line = linecache.getline(filename, line) + column = re.search("def", source_line).start(0) + source_buffer = source.Buffer(source_line, filename, line) + loc = source.Range(source_buffer, column, column) + return diagnostic.Diagnostic("note", + "definition of function '{function}'", + {"function": name}, + loc) + + def _type_of_param(self, function, loc, param): + if param.default is not inspect.Parameter.empty: + # 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. + synthesizer = ASTSynthesizer() + ast = synthesizer.quote(param.default) + synthesizer.finalize() + + def proxy_diagnostic(diag): + note = diagnostic.Diagnostic("note", + "expanded from here while trying to infer a type for an" + " unannotated optional argument '{param_name}' from its default value", + {"param_name": param.name}, + loc) + diag.notes.append(note) + + diag.notes.append(self._function_def_note(function)) + + self.engine.process(diag) + + proxy_engine = diagnostic.Engine() + proxy_engine.process = proxy_diagnostic + Inferencer(engine=proxy_engine).visit(ast) + IntMonomorphizer(engine=proxy_engine).visit(ast) + + return ast.type + else: + # Let the rest of the program decide. + return types.TVar() + + def _quote_rpc_function(self, function, loc): + signature = inspect.signature(function) + + arg_types = OrderedDict() + optarg_types = OrderedDict() + for param in signature.parameters.values(): + if param.kind not in (inspect.Parameter.POSITIONAL_ONLY, + inspect.Parameter.POSITIONAL_OR_KEYWORD): + # We pretend we don't see *args, kwpostargs=..., **kwargs. + # Since every method can be still invoked without any arguments + # going into *args and the slots after it, this is always safe, + # if sometimes constraining. + # + # Accepting POSITIONAL_ONLY is OK, because the compiler + # desugars the keyword arguments into positional ones internally. + continue + + if param.default is inspect.Parameter.empty: + arg_types[param.name] = self._type_of_param(function, loc, param) + else: + optarg_types[param.name] = self._type_of_param(function, loc, param) + + # Fixed for now. + ret_type = builtins.TInt(types.TValue(32)) + + rpc_type = types.TRPCFunction(arg_types, optarg_types, ret_type, + service=self._map(function)) + + rpc_name = "__rpc_{}__".format(rpc_type.service) + self.globals[rpc_name] = rpc_type + self.functions[function] = rpc_name + + return rpc_name + + def _quote_function(self, function, loc): if function in self.functions: return self.functions[function] - # Insert the typed AST for the new function and restart inference. - # It doesn't really matter where we insert as long as it is before - # the final call. - function_node = self._quote_embedded_function(function) - self.typedtree.insert(0, function_node) - self.inference_finished = False - return function_node.name + if hasattr(function, "artiq_embedded"): + # Insert the typed AST for the new function and restart inference. + # It doesn't really matter where we insert as long as it is before + # the final call. + function_node = self._quote_embedded_function(function) + self.typedtree.insert(0, function_node) + self.inference_finished = False + return function_node.name + else: + # Insert a storage-less global whose type instructs the compiler + # to perform an RPC instead of a regular call. + return self._quote_rpc_function(function, loc) def stitch_call(self, function, args, kwargs): function_node = self._quote_embedded_function(function) diff --git a/artiq/compiler/module.py b/artiq/compiler/module.py index 7f77c04dd..f6285540a 100644 --- a/artiq/compiler/module.py +++ b/artiq/compiler/module.py @@ -41,14 +41,16 @@ class Source: class Module: def __init__(self, src): - int_monomorphizer = transforms.IntMonomorphizer(engine=src.engine) - inferencer = transforms.Inferencer(engine=src.engine) - monomorphism_validator = validators.MonomorphismValidator(engine=src.engine) - escape_validator = validators.EscapeValidator(engine=src.engine) - artiq_ir_generator = transforms.ARTIQIRGenerator(engine=src.engine, + self.engine = src.engine + + int_monomorphizer = transforms.IntMonomorphizer(engine=self.engine) + inferencer = transforms.Inferencer(engine=self.engine) + monomorphism_validator = validators.MonomorphismValidator(engine=self.engine) + escape_validator = validators.EscapeValidator(engine=self.engine) + artiq_ir_generator = transforms.ARTIQIRGenerator(engine=self.engine, module_name=src.name) - dead_code_eliminator = transforms.DeadCodeEliminator(engine=src.engine) - local_access_validator = validators.LocalAccessValidator(engine=src.engine) + dead_code_eliminator = transforms.DeadCodeEliminator(engine=self.engine) + local_access_validator = validators.LocalAccessValidator(engine=self.engine) self.name = src.name self.globals = src.globals @@ -62,7 +64,8 @@ class Module: def build_llvm_ir(self, target): """Compile the module to LLVM IR for the specified target.""" - llvm_ir_generator = transforms.LLVMIRGenerator(module_name=self.name, target=target) + llvm_ir_generator = transforms.LLVMIRGenerator(engine=self.engine, + module_name=self.name, target=target) return llvm_ir_generator.process(self.artiq_ir) def entry_point(self): diff --git a/artiq/compiler/transforms/llvm_ir_generator.py b/artiq/compiler/transforms/llvm_ir_generator.py index bccc2397c..ca6e8da35 100644 --- a/artiq/compiler/transforms/llvm_ir_generator.py +++ b/artiq/compiler/transforms/llvm_ir_generator.py @@ -3,12 +3,13 @@ into LLVM intermediate representation. """ -from pythonparser import ast +from pythonparser import ast, diagnostic from llvmlite_artiq import ir as ll from .. import types, builtins, ir class LLVMIRGenerator: - def __init__(self, module_name, target): + def __init__(self, engine, module_name, target): + self.engine = engine self.target = target self.llcontext = target.llcontext self.llmodule = ll.Module(context=self.llcontext, name=module_name) @@ -21,6 +22,11 @@ class LLVMIRGenerator: 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_function(typ): + if for_return: + return ll.VoidType() + else: + return ll.LiteralStructType([]) elif types.is_function(typ): envarg = ll.IntType(8).as_pointer() llty = ll.FunctionType(args=[envarg] + @@ -89,10 +95,13 @@ class LLVMIRGenerator: return ll.Constant(llty, False) elif isinstance(const.value, (int, float)): return ll.Constant(llty, const.value) - elif isinstance(const.value, str): - assert "\0" not in const.value + elif isinstance(const.value, (str, bytes)): + if isinstance(const.value, str): + assert "\0" not in const.value + as_bytes = (const.value + "\0").encode("utf-8") + else: + as_bytes = const.value - as_bytes = (const.value + "\0").encode("utf-8") if ir.is_exn_typeinfo(const.type): # Exception typeinfo; should be merged with identical others name = "__artiq_exn_" + const.value @@ -144,6 +153,9 @@ class LLVMIRGenerator: llty = ll.FunctionType(ll.VoidType(), [self.llty_of_type(builtins.TException())]) elif name == "__artiq_reraise": llty = ll.FunctionType(ll.VoidType(), []) + elif name == "rpc": + llty = ll.FunctionType(ll.IntType(32), [ll.IntType(32), ll.IntType(8).as_pointer()], + var_arg=True) else: assert False @@ -546,11 +558,79 @@ class LLVMIRGenerator: name=insn.name) return llvalue + # See session.c:send_rpc_value. + def _rpc_tag(self, typ, root_type, root_loc): + if types.is_tuple(typ): + assert len(typ.elts) < 256 + return b"t" + bytes([len(typ.elts)]) + \ + b"".join([self._rpc_tag(elt_type, root_type, root_loc) + 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_list(typ): + return b"l" + self._rpc_tag(builtins.get_iterable_elt(typ), + root_type, root_loc) + elif builtins.is_range(typ): + return b"r" + self._rpc_tag(builtins.get_iterable_elt(typ), + root_type, root_loc) + elif ir.is_option(typ): + return b"o" + self._rpc_tag(typ.params["inner"], + root_type, root_loc) + else: + printer = types.TypePrinter() + note = diagnostic.Diagnostic("note", + "value of type {type}", + {"type": printer.name(root_type)}, + root_loc) + diag = diagnostic.Diagnostic("error", + "type {type} is not supported in remote procedure calls", + {"type": printer.name(typ)}, + root_loc) + self.engine.process(diag) + + def _build_rpc(self, service, args, return_type): + llservice = ll.Constant(ll.IntType(32), service) + + tag = b"" + for arg in args: + if isinstance(arg, ir.Constant): + # Constants don't have locations, but conveniently + # they also never fail to serialize. + tag += self._rpc_tag(arg.type, arg.type, None) + else: + tag += self._rpc_tag(arg.type, arg.type, arg.loc) + tag += b":\x00" + lltag = self.llconst_of_const(ir.Constant(tag, builtins.TStr())) + + llargs = [] + for arg in args: + llarg = self.map(arg) + llargslot = self.llbuilder.alloca(llarg.type) + self.llbuilder.store(llarg, llargslot) + llargs.append(llargslot) + + return self.llbuiltin("rpc"), [llservice, lltag] + llargs + def prepare_call(self, insn): - llclosure, llargs = self.map(insn.target_function()), map(self.map, insn.arguments()) - llenv = self.llbuilder.extract_value(llclosure, 0) - llfun = self.llbuilder.extract_value(llclosure, 1) - return llfun, [llenv] + list(llargs) + if types.is_rpc_function(insn.target_function().type): + return self._build_rpc(insn.target_function().type.service, + insn.arguments(), + insn.target_function().type.ret) + else: + llclosure, llargs = self.map(insn.target_function()), map(self.map, insn.arguments()) + llenv = self.llbuilder.extract_value(llclosure, 0) + llfun = self.llbuilder.extract_value(llclosure, 1) + return llfun, [llenv] + list(llargs) def process_Call(self, insn): llfun, llargs = self.prepare_call(insn) diff --git a/artiq/compiler/types.py b/artiq/compiler/types.py index a614ce637..4c145720c 100644 --- a/artiq/compiler/types.py +++ b/artiq/compiler/types.py @@ -222,6 +222,26 @@ class TFunction(Type): def __ne__(self, other): return not (self == other) +class TRPCFunction(TFunction): + """ + A function type of a remote function. + + :ivar service: (int) RPC service number + """ + + def __init__(self, args, optargs, ret, service): + super().__init__(args, optargs, ret) + self.service = service + + def unify(self, other): + if isinstance(other, TRPCFunction) and \ + self.service == other.service: + super().unify(other) + elif isinstance(other, TVar): + other.unify(self) + else: + raise UnificationError(self, other) + class TBuiltin(Type): """ An instance of builtin type. Every instance of a builtin @@ -310,6 +330,8 @@ def is_mono(typ, name=None, **params): typ = typ.find() params_match = True for param in params: + if param not in typ.params: + return False params_match = params_match and \ typ.params[param].find() == params[param].find() return isinstance(typ, TMono) and \ @@ -329,6 +351,9 @@ def is_tuple(typ, elts=None): def is_function(typ): return isinstance(typ.find(), TFunction) +def is_rpc_function(typ): + return isinstance(typ.find(), TRPCFunction) + def is_builtin(typ, name=None): typ = typ.find() if name is None: @@ -381,11 +406,16 @@ class TypePrinter(object): return "(%s,)" % self.name(typ.elts[0]) else: return "(%s)" % ", ".join(list(map(self.name, typ.elts))) - elif isinstance(typ, TFunction): + elif isinstance(typ, (TFunction, TRPCFunction)): args = [] args += [ "%s:%s" % (arg, self.name(typ.args[arg])) for arg in typ.args] args += ["?%s:%s" % (arg, self.name(typ.optargs[arg])) for arg in typ.optargs] - return "(%s)->%s" % (", ".join(args), self.name(typ.ret)) + signature = "(%s)->%s" % (", ".join(args), self.name(typ.ret)) + + if isinstance(typ, TRPCFunction): + return "rpc({}) {}".format(typ.service, signature) + elif isinstance(typ, TFunction): + return signature elif isinstance(typ, TBuiltinFunction): return "" % typ.name elif isinstance(typ, (TConstructor, TExceptionConstructor)): diff --git a/artiq/coredevice/comm_generic.py b/artiq/coredevice/comm_generic.py index df0cbb4cd..54246d525 100644 --- a/artiq/coredevice/comm_generic.py +++ b/artiq/coredevice/comm_generic.py @@ -276,8 +276,16 @@ class CommGeneric: self._write_empty(_H2DMsgType.RUN_KERNEL) logger.debug("running kernel") - def _receive_rpc_value(self, tag, rpc_map): - if tag == "n": + _rpc_sentinel = object() + + def _receive_rpc_value(self, rpc_map): + tag = chr(self._read_int8()) + if tag == "\x00": + return self._rpc_sentinel + elif tag == "t": + length = self._read_int8() + return tuple(self._receive_rpc_value(rpc_map) for _ in range(length)) + elif tag == "n": return None elif tag == "b": return bool(self._read_int8()) @@ -291,31 +299,36 @@ class CommGeneric: numerator = self._read_int64() denominator = self._read_int64() return Fraction(numerator, denominator) + elif tag == "s": + return self._read_string() elif tag == "l": - elt_tag = chr(self._read_int8()) length = self._read_int32() - return [self._receive_rpc_value(elt_tag) for _ in range(length)] + return [self._receive_rpc_value(rpc_map) for _ in range(length)] + elif tag == "r": + lower = self._receive_rpc_value(rpc_map) + upper = self._receive_rpc_value(rpc_map) + step = self._receive_rpc_value(rpc_map) + return range(lower, upper, step) elif tag == "o": return rpc_map[self._read_int32()] else: - raise IOError("Unknown RPC value tag: {}", tag) + raise IOError("Unknown RPC value tag: {}".format(repr(tag))) - def _receive_rpc_values(self, rpc_map): - result = [] + def _receive_rpc_args(self, rpc_map): + args = [] while True: - tag = chr(self._read_int8()) - if tag == "\x00": - return result - else: - result.append(self._receive_rpc_value(tag, rpc_map)) + value = self._receive_rpc_value(rpc_map) + if value is self._rpc_sentinel: + return args + args.append(value) def _serve_rpc(self, rpc_map): service = self._read_int32() - args = self._receive_rpc_values(rpc_map) + args = self._receive_rpc_args(rpc_map) logger.debug("rpc service: %d %r", service, args) try: - result = rpc_map[rpc_num](args) + result = rpc_map[service](*args) if not isinstance(result, int) or not (-2**31 < result < 2**31-1): raise ValueError("An RPC must return an int(width=32)") except ARTIQException as exn: diff --git a/artiq/coredevice/core.py b/artiq/coredevice/core.py index ce9ae391b..5188de1ae 100644 --- a/artiq/coredevice/core.py +++ b/artiq/coredevice/core.py @@ -50,13 +50,13 @@ class Core: raise CompileError() from error def run(self, function, args, kwargs): + kernel_library, rpc_map = self.compile(function, args, kwargs) + if self.first_run: self.comm.check_ident() self.comm.switch_clock(self.external_clock) self.first_run = False - kernel_library, rpc_map = self.compile(function, args, kwargs) - try: self.comm.load(kernel_library) except Exception as error: diff --git a/soc/runtime/ksupport.c b/soc/runtime/ksupport.c index b2082beef..9a866908c 100644 --- a/soc/runtime/ksupport.c +++ b/soc/runtime/ksupport.c @@ -301,33 +301,34 @@ void watchdog_clear(int id) mailbox_send_and_wait(&request); } -int rpc(int rpc_num, ...) +int rpc(int service, const char *tag, ...) { - struct msg_rpc_request request; + struct msg_rpc_send_request request; struct msg_base *reply; - request.type = MESSAGE_TYPE_RPC_REQUEST; - request.rpc_num = rpc_num; - va_start(request.args, rpc_num); + request.type = MESSAGE_TYPE_RPC_SEND_REQUEST; + request.service = service; + request.tag = tag; + va_start(request.args, tag); mailbox_send_and_wait(&request); va_end(request.args); reply = mailbox_wait_and_receive(); - if(reply->type == MESSAGE_TYPE_RPC_REPLY) { - int result = ((struct msg_rpc_reply *)reply)->result; - mailbox_acknowledge(); - return result; - } else if(reply->type == MESSAGE_TYPE_RPC_EXCEPTION) { - struct artiq_exception exception; - memcpy(&exception, ((struct msg_rpc_exception *)reply)->exception, - sizeof(struct artiq_exception)); - mailbox_acknowledge(); - __artiq_raise(&exception); - } else { + // if(reply->type == MESSAGE_TYPE_RPC_REPLY) { + // int result = ((struct msg_rpc_reply *)reply)->result; + // mailbox_acknowledge(); + // return result; + // } else if(reply->type == MESSAGE_TYPE_RPC_EXCEPTION) { + // struct artiq_exception exception; + // memcpy(&exception, ((struct msg_rpc_exception *)reply)->exception, + // sizeof(struct artiq_exception)); + // mailbox_acknowledge(); + // __artiq_raise(&exception); + // } else { log("Malformed MESSAGE_TYPE_RPC_REQUEST reply type %d", reply->type); while(1); - } + // } } void lognonl(const char *fmt, ...) diff --git a/soc/runtime/ksupport.h b/soc/runtime/ksupport.h index 47b9c3a1d..41561330f 100644 --- a/soc/runtime/ksupport.h +++ b/soc/runtime/ksupport.h @@ -5,7 +5,7 @@ long long int now_init(void); void now_save(long long int now); int watchdog_set(int ms); void watchdog_clear(int id); -int rpc(int service, ...); +int rpc(int service, const char *tag, ...); void lognonl(const char *fmt, ...); void log(const char *fmt, ...); diff --git a/soc/runtime/messages.h b/soc/runtime/messages.h index 651c7a5ef..55d53aca9 100644 --- a/soc/runtime/messages.h +++ b/soc/runtime/messages.h @@ -14,8 +14,9 @@ enum { MESSAGE_TYPE_WATCHDOG_SET_REQUEST, MESSAGE_TYPE_WATCHDOG_SET_REPLY, MESSAGE_TYPE_WATCHDOG_CLEAR, - MESSAGE_TYPE_RPC_REQUEST, - MESSAGE_TYPE_RPC_REPLY, + MESSAGE_TYPE_RPC_SEND_REQUEST, + MESSAGE_TYPE_RPC_RECV_REQUEST, + MESSAGE_TYPE_RPC_RECV_REPLY, MESSAGE_TYPE_RPC_EXCEPTION, MESSAGE_TYPE_LOG, @@ -80,15 +81,21 @@ struct msg_watchdog_clear { int id; }; -struct msg_rpc_request { +struct msg_rpc_send_request { int type; - int rpc_num; + int service; + const char *tag; va_list args; }; -struct msg_rpc_reply { +struct msg_rpc_recv_request { int type; - int result; + // TODO ??? +}; + +struct msg_rpc_recv_reply { + int type; + // TODO ??? }; struct msg_rpc_exception { diff --git a/soc/runtime/session.c b/soc/runtime/session.c index b571c5873..eadaa1990 100644 --- a/soc/runtime/session.c +++ b/soc/runtime/session.c @@ -457,23 +457,23 @@ static int process_input(void) user_kernel_state = USER_KERNEL_RUNNING; break; - case REMOTEMSG_TYPE_RPC_REPLY: { - struct msg_rpc_reply reply; + // case REMOTEMSG_TYPE_RPC_REPLY: { + // struct msg_rpc_reply reply; - int result = in_packet_int32(); + // int result = in_packet_int32(); - if(user_kernel_state != USER_KERNEL_WAIT_RPC) { - log("Unsolicited RPC reply"); - return 0; // restart session - } + // if(user_kernel_state != USER_KERNEL_WAIT_RPC) { + // log("Unsolicited RPC reply"); + // return 0; // restart session + // } - reply.type = MESSAGE_TYPE_RPC_REPLY; - reply.result = result; - mailbox_send_and_wait(&reply); + // reply.type = MESSAGE_TYPE_RPC_REPLY; + // reply.result = result; + // mailbox_send_and_wait(&reply); - user_kernel_state = USER_KERNEL_RUNNING; - break; - } + // user_kernel_state = USER_KERNEL_RUNNING; + // break; + // } case REMOTEMSG_TYPE_RPC_EXCEPTION: { struct msg_rpc_exception reply; @@ -509,91 +509,156 @@ static int process_input(void) return 1; } -static int send_rpc_value(const char **tag, void *value) +// See llvm_ir_generator.py:_rpc_tag. +static int send_rpc_value(const char **tag, void **value) { if(!out_packet_int8(**tag)) - return -1; + return 0; + + switch(*(*tag)++) { + case 't': { // tuple + int size = *(*tag)++; + if(!out_packet_int8(size)) + return 0; + + for(int i = 0; i < size; i++) { + if(!send_rpc_value(tag, value)) + return 0; + } + break; + } - int size = 0; - switch(**tag) { - case 0: // last tag case 'n': // None break; - case 'b': // bool - size = 1; - if(!out_packet_chunk(value, size)) - return -1; + case 'b': { // bool + int size = sizeof(int8_t); + if(!out_packet_chunk(*value, size)) + return 0; + *value = (void*)((intptr_t)(*value) + size); break; + } - case 'i': // int(width=32) - size = 4; - if(!out_packet_chunk(value, size)) - return -1; + case 'i': { // int(width=32) + int size = sizeof(int32_t); + if(!out_packet_chunk(*value, size)) + return 0; + *value = (void*)((intptr_t)(*value) + size); break; + } - case 'I': // int(width=64) - case 'f': // float - size = 8; - if(!out_packet_chunk(value, size)) - return -1; + case 'I': { // int(width=64) + int size = sizeof(int64_t); + if(!out_packet_chunk(*value, size)) + return 0; + *value = (void*)((intptr_t)(*value) + size); break; + } - case 'F': // Fraction - size = 16; - if(!out_packet_chunk(value, size)) - return -1; + case 'f': { // float + int size = sizeof(double); + if(!out_packet_chunk(*value, size)) + return 0; + *value = (void*)((intptr_t)(*value) + size); break; + } + + case 'F': { // Fraction + int size = sizeof(int64_t) * 2; + if(!out_packet_chunk(*value, size)) + return 0; + *value = (void*)((intptr_t)(*value) + size); + break; + } + + case 's': { // string + const char **string = *value; + if(!out_packet_string(*string)) + return 0; + *value = (void*)((intptr_t)(*value) + strlen(*string) + 1); + break; + } case 'l': { // list(elt='a) - struct { uint32_t length; void *elements; } *list = value; + struct { uint32_t length; struct {} *elements; } *list = *value; void *element = list->elements; - const char *tag_copy = *tag + 1; + if(!out_packet_int32(list->length)) + return 0; + + const char *tag_copy; for(int i = 0; i < list->length; i++) { - int element_size = send_rpc_value(&tag_copy, element); - if(element_size < 0) - return -1; - element = (void*)((intptr_t)element + element_size); + tag_copy = *tag; + if(!send_rpc_value(&tag_copy, &element)) + return 0; } *tag = tag_copy; - size = sizeof(list); + *value = (void*)((intptr_t)(*value) + sizeof(*list)); break; } - case 'o': { // host object - struct { uint32_t id; } *object = value; - - if(!out_packet_int32(object->id)) - return -1; - - size = sizeof(object); + case 'r': { // range(elt='a) + const char *tag_copy; + tag_copy = *tag; + if(!send_rpc_value(&tag_copy, value)) // min + return 0; + tag_copy = *tag; + if(!send_rpc_value(&tag_copy, value)) // max + return 0; + tag_copy = *tag; + if(!send_rpc_value(&tag_copy, value)) // step + return 0; + *tag = tag_copy; break; } + case 'o': { // option(inner='a) + struct { int8_t present; struct {} contents; } *option = *value; + void *contents = &option->contents; + + if(!out_packet_int8(option->present)) + return 0; + + // option never appears in composite types, so we don't have + // to accurately advance *value. + if(option->present) { + return send_rpc_value(tag, &contents); + } else { + (*tag)++; + break; + } + } + + case 'O': { // host object + struct { uint32_t id; } **object = *value; + + if(!out_packet_int32((*object)->id)) + return 0; + } + default: - return -1; + log("send_rpc_value: unknown tag %02x", *((*tag) - 1)); + return 0; } - (*tag)++; - return size; + return 1; } -static int send_rpc_request(int service, va_list args) +static int send_rpc_request(int service, const char *tag, va_list args) { out_packet_start(REMOTEMSG_TYPE_RPC_REQUEST); out_packet_int32(service); - const char *tag = va_arg(args, const char*); - while(*tag) { + while(*tag != ':') { void *value = va_arg(args, void*); if(!kloader_validate_kpointer(value)) return 0; - if(send_rpc_value(&tag, &value) < 0) + if(!send_rpc_value(&tag, &value)) return 0; } + out_packet_int8(0); out_packet_finish(); return 1; } @@ -670,10 +735,10 @@ static int process_kmsg(struct msg_base *umsg) break; } - case MESSAGE_TYPE_RPC_REQUEST: { - struct msg_rpc_request *msg = (struct msg_rpc_request *)umsg; + case MESSAGE_TYPE_RPC_SEND_REQUEST: { + struct msg_rpc_send_request *msg = (struct msg_rpc_send_request *)umsg; - if(!send_rpc_request(msg->rpc_num, msg->args)) { + if(!send_rpc_request(msg->service, msg->tag, msg->args)) { log("Failed to send RPC request"); return 0; // restart session } From 153592f1cce11a1e375ce0d4233e6e912e292b16 Mon Sep 17 00:00:00 2001 From: whitequark Date: Sun, 9 Aug 2015 02:25:58 +0300 Subject: [PATCH 197/369] Naming. --- artiq/compiler/transforms/llvm_ir_generator.py | 6 +++--- soc/runtime/ksupport.c | 16 ++++++++-------- soc/runtime/ksupport.h | 2 +- soc/runtime/messages.h | 4 ++-- soc/runtime/session.c | 6 +++--- 5 files changed, 17 insertions(+), 17 deletions(-) diff --git a/artiq/compiler/transforms/llvm_ir_generator.py b/artiq/compiler/transforms/llvm_ir_generator.py index ca6e8da35..9928c560f 100644 --- a/artiq/compiler/transforms/llvm_ir_generator.py +++ b/artiq/compiler/transforms/llvm_ir_generator.py @@ -153,7 +153,7 @@ class LLVMIRGenerator: llty = ll.FunctionType(ll.VoidType(), [self.llty_of_type(builtins.TException())]) elif name == "__artiq_reraise": llty = ll.FunctionType(ll.VoidType(), []) - elif name == "rpc": + elif name == "send_rpc": llty = ll.FunctionType(ll.IntType(32), [ll.IntType(32), ll.IntType(8).as_pointer()], var_arg=True) else: @@ -609,7 +609,7 @@ class LLVMIRGenerator: tag += self._rpc_tag(arg.type, arg.type, None) else: tag += self._rpc_tag(arg.type, arg.type, arg.loc) - tag += b":\x00" + tag += b"\x00" lltag = self.llconst_of_const(ir.Constant(tag, builtins.TStr())) llargs = [] @@ -619,7 +619,7 @@ class LLVMIRGenerator: self.llbuilder.store(llarg, llargslot) llargs.append(llargslot) - return self.llbuiltin("rpc"), [llservice, lltag] + llargs + return self.llbuiltin("send_rpc"), [llservice, lltag] + llargs def prepare_call(self, insn): if types.is_rpc_function(insn.target_function().type): diff --git a/soc/runtime/ksupport.c b/soc/runtime/ksupport.c index 9a866908c..187e33220 100644 --- a/soc/runtime/ksupport.c +++ b/soc/runtime/ksupport.c @@ -92,7 +92,7 @@ static const struct symbol runtime_exports[] = { {"log", &log}, {"lognonl", &lognonl}, - {"rpc", &rpc}, + {"send_rpc", &send_rpc}, /* direct syscalls */ {"rtio_get_counter", &rtio_get_counter}, @@ -301,19 +301,19 @@ void watchdog_clear(int id) mailbox_send_and_wait(&request); } -int rpc(int service, const char *tag, ...) +int send_rpc(int service, const char *tag, ...) { - struct msg_rpc_send_request request; - struct msg_base *reply; + struct msg_rpc_send request; - request.type = MESSAGE_TYPE_RPC_SEND_REQUEST; + request.type = MESSAGE_TYPE_RPC_SEND; request.service = service; request.tag = tag; va_start(request.args, tag); mailbox_send_and_wait(&request); va_end(request.args); - reply = mailbox_wait_and_receive(); + // struct msg_base *reply; + // reply = mailbox_wait_and_receive(); // if(reply->type == MESSAGE_TYPE_RPC_REPLY) { // int result = ((struct msg_rpc_reply *)reply)->result; // mailbox_acknowledge(); @@ -325,8 +325,8 @@ int rpc(int service, const char *tag, ...) // mailbox_acknowledge(); // __artiq_raise(&exception); // } else { - log("Malformed MESSAGE_TYPE_RPC_REQUEST reply type %d", - reply->type); + // log("Malformed MESSAGE_TYPE_RPC_REQUEST reply type %d", + // reply->type); while(1); // } } diff --git a/soc/runtime/ksupport.h b/soc/runtime/ksupport.h index 41561330f..2aa83ce63 100644 --- a/soc/runtime/ksupport.h +++ b/soc/runtime/ksupport.h @@ -5,7 +5,7 @@ long long int now_init(void); void now_save(long long int now); int watchdog_set(int ms); void watchdog_clear(int id); -int rpc(int service, const char *tag, ...); +int send_rpc(int service, const char *tag, ...); void lognonl(const char *fmt, ...); void log(const char *fmt, ...); diff --git a/soc/runtime/messages.h b/soc/runtime/messages.h index 55d53aca9..46294f8e6 100644 --- a/soc/runtime/messages.h +++ b/soc/runtime/messages.h @@ -14,7 +14,7 @@ enum { MESSAGE_TYPE_WATCHDOG_SET_REQUEST, MESSAGE_TYPE_WATCHDOG_SET_REPLY, MESSAGE_TYPE_WATCHDOG_CLEAR, - MESSAGE_TYPE_RPC_SEND_REQUEST, + MESSAGE_TYPE_RPC_SEND, MESSAGE_TYPE_RPC_RECV_REQUEST, MESSAGE_TYPE_RPC_RECV_REPLY, MESSAGE_TYPE_RPC_EXCEPTION, @@ -81,7 +81,7 @@ struct msg_watchdog_clear { int id; }; -struct msg_rpc_send_request { +struct msg_rpc_send { int type; int service; const char *tag; diff --git a/soc/runtime/session.c b/soc/runtime/session.c index eadaa1990..f5e846477 100644 --- a/soc/runtime/session.c +++ b/soc/runtime/session.c @@ -650,7 +650,7 @@ static int send_rpc_request(int service, const char *tag, va_list args) out_packet_start(REMOTEMSG_TYPE_RPC_REQUEST); out_packet_int32(service); - while(*tag != ':') { + while(*tag) { void *value = va_arg(args, void*); if(!kloader_validate_kpointer(value)) return 0; @@ -735,8 +735,8 @@ static int process_kmsg(struct msg_base *umsg) break; } - case MESSAGE_TYPE_RPC_SEND_REQUEST: { - struct msg_rpc_send_request *msg = (struct msg_rpc_send_request *)umsg; + case MESSAGE_TYPE_RPC_SEND: { + struct msg_rpc_send *msg = (struct msg_rpc_send *)umsg; if(!send_rpc_request(msg->service, msg->tag, msg->args)) { log("Failed to send RPC request"); From 9c5ca2ae29893c6ff06fda22b63b2483d11406db Mon Sep 17 00:00:00 2001 From: whitequark Date: Sun, 9 Aug 2015 14:39:21 +0300 Subject: [PATCH 198/369] LLVMIRGenerator: add target data layout to LLVM modules. --- artiq/compiler/targets.py | 4 ++++ artiq/compiler/transforms/llvm_ir_generator.py | 1 + 2 files changed, 5 insertions(+) diff --git a/artiq/compiler/targets.py b/artiq/compiler/targets.py index 1b89e039e..b587e3d6e 100644 --- a/artiq/compiler/targets.py +++ b/artiq/compiler/targets.py @@ -12,6 +12,8 @@ class Target: :var triple: (string) LLVM target triple, e.g. ``"or1k"`` + :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) @@ -19,6 +21,7 @@ class Target: provided by the target, e.g. ``"printf"``. """ triple = "unknown" + data_layout = "" features = [] print_function = "printf" @@ -82,5 +85,6 @@ class NativeTarget(Target): class OR1KTarget(Target): triple = "or1k-linux" + data_layout = "E-m:e-p:32:32-i64:32-f64:32-v64:32-v128:32-a:0:32-n32" attributes = ["mul", "div", "ffl1", "cmov", "addc"] print_function = "lognonl" diff --git a/artiq/compiler/transforms/llvm_ir_generator.py b/artiq/compiler/transforms/llvm_ir_generator.py index 9928c560f..6233c103f 100644 --- a/artiq/compiler/transforms/llvm_ir_generator.py +++ b/artiq/compiler/transforms/llvm_ir_generator.py @@ -14,6 +14,7 @@ class LLVMIRGenerator: 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.llfunction = None self.llmap = {} self.fixups = [] From 8b7d38d20357204eaa08f7c94dae27678b628715 Mon Sep 17 00:00:00 2001 From: whitequark Date: Sun, 9 Aug 2015 15:47:29 +0300 Subject: [PATCH 199/369] Add ARTIQ_DUMP_ASSEMBLY. --- artiq/compiler/targets.py | 21 ++++++++++++++++++++- artiq/coredevice/core.py | 11 +---------- 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/artiq/compiler/targets.py b/artiq/compiler/targets.py index b587e3d6e..a023945f7 100644 --- a/artiq/compiler/targets.py +++ b/artiq/compiler/targets.py @@ -1,4 +1,4 @@ -import tempfile, subprocess +import os, sys, tempfile, subprocess from llvmlite_artiq import ir as ll, binding as llvm llvm.initialize() @@ -56,10 +56,20 @@ class Target: def compile(self, module): """Compile the module to a relocatable object for this target.""" + + if os.getenv('ARTIQ_DUMP_IR'): + print("====== ARTIQ IR DUMP ======", file=sys.stderr) + for function in module.artiq_ir: + print(function, file=sys.stderr) + llmod = module.build_llvm_ir(self) llparsedmod = llvm.parse_assembly(str(llmod)) llparsedmod.verify() + if os.getenv('ARTIQ_DUMP_LLVM'): + print("====== LLVM IR DUMP ======", file=sys.stderr) + print(str(llparsedmod), file=sys.stderr) + llpassmgrbuilder = llvm.create_pass_manager_builder() llpassmgrbuilder.opt_level = 2 # -O2 llpassmgrbuilder.size_level = 1 # -Os @@ -68,10 +78,19 @@ class Target: llpassmgrbuilder.populate(llpassmgr) llpassmgr.run(llparsedmod) + if os.getenv('ARTIQ_DUMP_LLVM'): + print("====== LLVM IR DUMP (OPTIMIZED) ======", file=sys.stderr) + print(str(llparsedmod), file=sys.stderr) + lltarget = llvm.Target.from_triple(self.triple) llmachine = lltarget.create_target_machine( features=",".join(self.features), reloc="pic", codemodel="default") + + if os.getenv('ARTIQ_DUMP_ASSEMBLY'): + print("====== ASSEMBLY DUMP ======", file=sys.stderr) + print(llmachine.emit_assembly(llparsedmod), file=sys.stderr) + return llmachine.emit_object(llparsedmod) def compile_and_link(self, modules): diff --git a/artiq/coredevice/core.py b/artiq/coredevice/core.py index 5188de1ae..91fa01562 100644 --- a/artiq/coredevice/core.py +++ b/artiq/coredevice/core.py @@ -1,4 +1,4 @@ -import os, sys, tempfile +import sys, tempfile from pythonparser import diagnostic @@ -35,15 +35,6 @@ class Core: module = Module(stitcher) target = OR1KTarget() - if os.getenv('ARTIQ_DUMP_IR'): - print("====== ARTIQ IR DUMP ======", file=sys.stderr) - for function in module.artiq_ir: - print(function, file=sys.stderr) - - if os.getenv('ARTIQ_DUMP_LLVM'): - print("====== LLVM IR DUMP ======", file=sys.stderr) - print(module.build_llvm_ir(target), file=sys.stderr) - return target.compile_and_link([module]), stitcher.rpc_map except diagnostic.Error as error: print("\n".join(error.diagnostic.render(colored=True)), file=sys.stderr) From 02b1543c630132e402c7e5a27029568ae985c5f0 Mon Sep 17 00:00:00 2001 From: whitequark Date: Sun, 9 Aug 2015 16:16:41 +0300 Subject: [PATCH 200/369] Implement receiving exceptions from RPCs. --- .../compiler/transforms/llvm_ir_generator.py | 158 +++++++++++++----- artiq/coredevice/comm_generic.py | 8 +- soc/runtime/ksupport.c | 45 +++-- soc/runtime/ksupport.h | 3 +- soc/runtime/messages.h | 9 +- soc/runtime/session.c | 28 +++- 6 files changed, 177 insertions(+), 74 deletions(-) diff --git a/artiq/compiler/transforms/llvm_ir_generator.py b/artiq/compiler/transforms/llvm_ir_generator.py index 6233c103f..9cda1b803 100644 --- a/artiq/compiler/transforms/llvm_ir_generator.py +++ b/artiq/compiler/transforms/llvm_ir_generator.py @@ -146,6 +146,10 @@ class LLVMIRGenerator: 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 == "llvm.stacksave": + llty = ll.FunctionType(ll.IntType(8).as_pointer(), []) + elif name == "llvm.stackrestore": + llty = ll.FunctionType(ll.VoidType(), [ll.IntType(8).as_pointer()]) elif name == self.target.print_function: llty = ll.FunctionType(ll.VoidType(), [ll.IntType(8).as_pointer()], var_arg=True) elif name == "__artiq_personality": @@ -155,8 +159,10 @@ class LLVMIRGenerator: elif name == "__artiq_reraise": llty = ll.FunctionType(ll.VoidType(), []) elif name == "send_rpc": - llty = ll.FunctionType(ll.IntType(32), [ll.IntType(32), ll.IntType(8).as_pointer()], + llty = ll.FunctionType(ll.VoidType(), [ll.IntType(32), ll.IntType(8).as_pointer()], var_arg=True) + elif name == "recv_rpc": + llty = ll.FunctionType(ll.IntType(32), [ll.IntType(8).as_pointer().as_pointer()]) else: assert False @@ -559,12 +565,18 @@ class LLVMIRGenerator: name=insn.name) return llvalue - # See session.c:send_rpc_value. - def _rpc_tag(self, typ, root_type, root_loc): + def _prepare_closure_call(self, insn): + llclosure, llargs = self.map(insn.target_function()), map(self.map, insn.arguments()) + llenv = self.llbuilder.extract_value(llclosure, 0) + llfun = self.llbuilder.extract_value(llclosure, 1) + return llfun, [llenv] + list(llargs) + + # See session.c:send_rpc_value and session.c:recv_rpc_value. + def _rpc_tag(self, typ, error_handler): if types.is_tuple(typ): assert len(typ.elts) < 256 return b"t" + bytes([len(typ.elts)]) + \ - b"".join([self._rpc_tag(elt_type, root_type, root_loc) + b"".join([self._rpc_tag(elt_type, error_handler) for elt_type in typ.elts]) elif builtins.is_none(typ): return b"n" @@ -580,38 +592,53 @@ class LLVMIRGenerator: return b"s" elif builtins.is_list(typ): return b"l" + self._rpc_tag(builtins.get_iterable_elt(typ), - root_type, root_loc) + error_handler) elif builtins.is_range(typ): return b"r" + self._rpc_tag(builtins.get_iterable_elt(typ), - root_type, root_loc) + error_handler) elif ir.is_option(typ): return b"o" + self._rpc_tag(typ.params["inner"], - root_type, root_loc) + error_handler) else: + error_handler(typ) + + def _build_rpc(self, fun_loc, fun_type, args, llnormalblock, llunwindblock): + llservice = ll.Constant(ll.IntType(32), 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.typ)}, + arg.loc) + self.engine.process(diag) + tag += self._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(root_type)}, - root_loc) - diag = diagnostic.Diagnostic("error", - "type {type} is not supported in remote procedure calls", {"type": printer.name(typ)}, - root_loc) + fun_loc) + diag = diagnostic.Diagnostic("error", + "return type {type} is not supported in remote procedure calls", + {"type": printer.name(fun_type.ret)}, + fun_loc) self.engine.process(diag) - - def _build_rpc(self, service, args, return_type): - llservice = ll.Constant(ll.IntType(32), service) - - tag = b"" - for arg in args: - if isinstance(arg, ir.Constant): - # Constants don't have locations, but conveniently - # they also never fail to serialize. - tag += self._rpc_tag(arg.type, arg.type, None) - else: - tag += self._rpc_tag(arg.type, arg.type, arg.loc) + tag += self._rpc_tag(fun_type.ret, ret_error_handler) tag += b"\x00" - lltag = self.llconst_of_const(ir.Constant(tag, builtins.TStr())) + + lltag = self.llconst_of_const(ir.Constant(tag + b"\x00", builtins.TStr())) + + llstackptr = self.llbuilder.call(self.llbuiltin("llvm.stacksave"), []) llargs = [] for arg in args: @@ -620,30 +647,79 @@ class LLVMIRGenerator: self.llbuilder.store(llarg, llargslot) llargs.append(llargslot) - return self.llbuiltin("send_rpc"), [llservice, lltag] + llargs + self.llbuilder.call(self.llbuiltin("send_rpc"), + [llservice, lltag] + llargs) - def prepare_call(self, insn): - if types.is_rpc_function(insn.target_function().type): - return self._build_rpc(insn.target_function().type.service, - insn.arguments(), - insn.target_function().type.ret) + # Don't waste stack space on saved arguments. + self.llbuilder.call(self.llbuiltin("llvm.stackrestore"), [llstackptr]) + + # T result = { + # void *ptr = NULL; + # loop: int size = rpc_recv("tag", ptr); + # if(size) { ptr = alloca(size); goto loop; } + # else *(T*)ptr + # } + llprehead = self.llbuilder.basic_block + llhead = self.llbuilder.append_basic_block(name=llprehead.name + ".rpc.head") + if llunwindblock: + llheadu = self.llbuilder.append_basic_block(name=llprehead.name + ".rpc.head.unwind") + llalloc = self.llbuilder.append_basic_block(name=llprehead.name + ".rpc.alloc") + lltail = self.llbuilder.append_basic_block(name=llprehead.name + ".rpc.tail") + + llslot = self.llbuilder.alloca(ll.IntType(8).as_pointer()) + self.llbuilder.store(ll.Constant(ll.IntType(8).as_pointer(), None), llslot) + self.llbuilder.branch(llhead) + + self.llbuilder.position_at_end(llhead) + if llunwindblock: + llsize = self.llbuilder.invoke(self.llbuiltin("recv_rpc"), [llslot], + llheadu, llunwindblock) + self.llbuilder.position_at_end(llheadu) else: - llclosure, llargs = self.map(insn.target_function()), map(self.map, insn.arguments()) - llenv = self.llbuilder.extract_value(llclosure, 0) - llfun = self.llbuilder.extract_value(llclosure, 1) - return llfun, [llenv] + list(llargs) + llsize = self.llbuilder.call(self.llbuiltin("recv_rpc"), [llslot]) + lldone = self.llbuilder.icmp_unsigned('==', llsize, ll.Constant(llsize.type, 0)) + self.llbuilder.cbranch(lldone, lltail, llalloc) + + self.llbuilder.position_at_end(llalloc) + llalloca = self.llbuilder.alloca(ll.IntType(8), llsize) + self.llbuilder.store(llalloca, llslot) + self.llbuilder.branch(llhead) + + self.llbuilder.position_at_end(lltail) + llretty = self.llty_of_type(fun_type.ret, for_return=True) + llretptr = self.llbuilder.bitcast(llslot, llretty.as_pointer()) + llret = self.llbuilder.load(llretptr) + if not builtins.is_allocated(fun_type.ret): + # 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): - llfun, llargs = self.prepare_call(insn) - return self.llbuilder.call(llfun, llargs, - name=insn.name) + if types.is_rpc_function(insn.target_function().type): + return self._build_rpc(insn.target_function().loc, + insn.target_function().type, + insn.arguments(), + llnormalblock=None, llunwindblock=None) + else: + llfun, llargs = self._prepare_closure_call(insn) + return self.llbuilder.call(llfun, llargs, + name=insn.name) def process_Invoke(self, insn): - llfun, llargs = self.prepare_call(insn) llnormalblock = self.map(insn.normal_target()) llunwindblock = self.map(insn.exception_target()) - return self.llbuilder.invoke(llfun, llargs, llnormalblock, llunwindblock, - name=insn.name) + if types.is_rpc_function(insn.target_function().type): + return self._build_rpc(insn.target_function().loc, + insn.target_function().type, + insn.arguments(), + llnormalblock, llunwindblock) + else: + llfun, llargs = self._prepare_closure_call(insn) + return self.llbuilder.invoke(llfun, llargs, llnormalblock, llunwindblock, + name=insn.name) def process_Select(self, insn): return self.llbuilder.select(self.map(insn.condition()), diff --git a/artiq/coredevice/comm_generic.py b/artiq/coredevice/comm_generic.py index 54246d525..267708afe 100644 --- a/artiq/coredevice/comm_generic.py +++ b/artiq/coredevice/comm_generic.py @@ -8,6 +8,7 @@ from artiq.language import core as core_language logger = logging.getLogger(__name__) +logger.setLevel(logging.DEBUG) class _H2DMsgType(Enum): @@ -325,13 +326,14 @@ class CommGeneric: def _serve_rpc(self, rpc_map): service = self._read_int32() args = self._receive_rpc_args(rpc_map) - logger.debug("rpc service: %d %r", service, args) + return_tag = self._read_string() + logger.debug("rpc service: %d %r -> %s", service, args, return_tag) try: result = rpc_map[service](*args) if not isinstance(result, int) or not (-2**31 < result < 2**31-1): raise ValueError("An RPC must return an int(width=32)") - except ARTIQException as exn: + except core_language.ARTIQException as exn: logger.debug("rpc service: %d %r ! %r", service, args, exn) self._write_header(_H2DMsgType.RPC_EXCEPTION) @@ -355,7 +357,7 @@ class CommGeneric: for index in range(3): self._write_int64(0) - ((filename, line, function, _), ) = traceback.extract_tb(exn.__traceback__) + (_, (filename, line, function, _), ) = traceback.extract_tb(exn.__traceback__, 2) self._write_string(filename) self._write_int32(line) self._write_int32(-1) # column not known diff --git a/soc/runtime/ksupport.c b/soc/runtime/ksupport.c index 187e33220..7a681bc59 100644 --- a/soc/runtime/ksupport.c +++ b/soc/runtime/ksupport.c @@ -93,6 +93,7 @@ static const struct symbol runtime_exports[] = { {"log", &log}, {"lognonl", &lognonl}, {"send_rpc", &send_rpc}, + {"recv_rpc", &recv_rpc}, /* direct syscalls */ {"rtio_get_counter", &rtio_get_counter}, @@ -301,7 +302,7 @@ void watchdog_clear(int id) mailbox_send_and_wait(&request); } -int send_rpc(int service, const char *tag, ...) +void send_rpc(int service, const char *tag, ...) { struct msg_rpc_send request; @@ -311,24 +312,34 @@ int send_rpc(int service, const char *tag, ...) va_start(request.args, tag); mailbox_send_and_wait(&request); va_end(request.args); +} - // struct msg_base *reply; - // reply = mailbox_wait_and_receive(); - // if(reply->type == MESSAGE_TYPE_RPC_REPLY) { - // int result = ((struct msg_rpc_reply *)reply)->result; - // mailbox_acknowledge(); - // return result; - // } else if(reply->type == MESSAGE_TYPE_RPC_EXCEPTION) { - // struct artiq_exception exception; - // memcpy(&exception, ((struct msg_rpc_exception *)reply)->exception, - // sizeof(struct artiq_exception)); - // mailbox_acknowledge(); - // __artiq_raise(&exception); - // } else { - // log("Malformed MESSAGE_TYPE_RPC_REQUEST reply type %d", - // reply->type); +int recv_rpc(void **slot) { + struct msg_rpc_recv_request request; + struct msg_rpc_recv_reply *reply; + + request.type = MESSAGE_TYPE_RPC_RECV_REQUEST; + request.slot = slot; + mailbox_send_and_wait(&request); + + reply = mailbox_wait_and_receive(); + if(reply->type != MESSAGE_TYPE_RPC_RECV_REPLY) { + log("Malformed MESSAGE_TYPE_RPC_RECV_REQUEST reply type %d", + reply->type); while(1); - // } + } + + if(reply->exception) { + struct artiq_exception exception; + memcpy(&exception, reply->exception, + sizeof(struct artiq_exception)); + mailbox_acknowledge(); + __artiq_raise(&exception); + } else { + int alloc_size = reply->alloc_size; + mailbox_acknowledge(); + return alloc_size; + } } void lognonl(const char *fmt, ...) diff --git a/soc/runtime/ksupport.h b/soc/runtime/ksupport.h index 2aa83ce63..2171324a4 100644 --- a/soc/runtime/ksupport.h +++ b/soc/runtime/ksupport.h @@ -5,7 +5,8 @@ long long int now_init(void); void now_save(long long int now); int watchdog_set(int ms); void watchdog_clear(int id); -int send_rpc(int service, const char *tag, ...); +void send_rpc(int service, const char *tag, ...); +int recv_rpc(void **slot); void lognonl(const char *fmt, ...); void log(const char *fmt, ...); diff --git a/soc/runtime/messages.h b/soc/runtime/messages.h index 46294f8e6..533f296ca 100644 --- a/soc/runtime/messages.h +++ b/soc/runtime/messages.h @@ -17,7 +17,6 @@ enum { MESSAGE_TYPE_RPC_SEND, MESSAGE_TYPE_RPC_RECV_REQUEST, MESSAGE_TYPE_RPC_RECV_REPLY, - MESSAGE_TYPE_RPC_EXCEPTION, MESSAGE_TYPE_LOG, MESSAGE_TYPE_BRG_READY, @@ -90,16 +89,12 @@ struct msg_rpc_send { struct msg_rpc_recv_request { int type; - // TODO ??? + void **slot; }; struct msg_rpc_recv_reply { int type; - // TODO ??? -}; - -struct msg_rpc_exception { - int type; + int alloc_size; struct artiq_exception *exception; }; diff --git a/soc/runtime/session.c b/soc/runtime/session.c index f5e846477..84e04265c 100644 --- a/soc/runtime/session.c +++ b/soc/runtime/session.c @@ -476,7 +476,8 @@ static int process_input(void) // } case REMOTEMSG_TYPE_RPC_EXCEPTION: { - struct msg_rpc_exception reply; + struct msg_rpc_recv_request *request; + struct msg_rpc_recv_reply reply; struct artiq_exception exception; exception.name = in_packet_string(); @@ -494,7 +495,15 @@ static int process_input(void) return 0; // restart session } - reply.type = MESSAGE_TYPE_RPC_EXCEPTION; + request = mailbox_wait_and_receive(); + if(request->type != MESSAGE_TYPE_RPC_RECV_REQUEST) { + log("Expected MESSAGE_TYPE_RPC_RECV_REQUEST, got %d", + request->type); + return 0; // restart session + } + + reply.type = MESSAGE_TYPE_RPC_RECV_REPLY; + reply.alloc_size = 0; reply.exception = &exception; mailbox_send_and_wait(&reply); @@ -650,15 +659,17 @@ static int send_rpc_request(int service, const char *tag, va_list args) out_packet_start(REMOTEMSG_TYPE_RPC_REQUEST); out_packet_int32(service); - while(*tag) { + while(*tag != ':') { void *value = va_arg(args, void*); if(!kloader_validate_kpointer(value)) return 0; if(!send_rpc_value(&tag, &value)) return 0; } - out_packet_int8(0); + + out_packet_string(tag + 1); + out_packet_finish(); return 1; } @@ -670,6 +681,12 @@ static int process_kmsg(struct msg_base *umsg) return 0; if(kloader_is_essential_kmsg(umsg->type)) return 1; /* handled elsewhere */ + if(user_kernel_state == USER_KERNEL_WAIT_RPC && + umsg->type == MESSAGE_TYPE_RPC_RECV_REQUEST) { + // Handled and acknowledged when we receive + // REMOTEMSG_TYPE_RPC_{EXCEPTION,REPLY}. + return 1; + } if(user_kernel_state != USER_KERNEL_RUNNING) { log("Received unexpected message from kernel CPU while not in running state"); return 0; @@ -739,7 +756,8 @@ static int process_kmsg(struct msg_base *umsg) struct msg_rpc_send *msg = (struct msg_rpc_send *)umsg; if(!send_rpc_request(msg->service, msg->tag, msg->args)) { - log("Failed to send RPC request"); + log("Failed to send RPC request (service %d, tag %s)", + msg->service, msg->tag); return 0; // restart session } From d4270cf66e8d82e4783a5f63fb8d4ecd6d278fa9 Mon Sep 17 00:00:00 2001 From: whitequark Date: Sun, 9 Aug 2015 20:17:00 +0300 Subject: [PATCH 201/369] Implement receiving data from RPCs. --- .../compiler/transforms/llvm_ir_generator.py | 21 +- artiq/coredevice/comm_generic.py | 114 +++++++- soc/runtime/ksupport.c | 2 +- soc/runtime/ksupport.h | 2 +- soc/runtime/messages.h | 2 +- soc/runtime/session.c | 271 ++++++++++++++---- 6 files changed, 337 insertions(+), 75 deletions(-) diff --git a/artiq/compiler/transforms/llvm_ir_generator.py b/artiq/compiler/transforms/llvm_ir_generator.py index 9cda1b803..3667e6474 100644 --- a/artiq/compiler/transforms/llvm_ir_generator.py +++ b/artiq/compiler/transforms/llvm_ir_generator.py @@ -162,7 +162,7 @@ class LLVMIRGenerator: llty = ll.FunctionType(ll.VoidType(), [ll.IntType(32), ll.IntType(8).as_pointer()], var_arg=True) elif name == "recv_rpc": - llty = ll.FunctionType(ll.IntType(32), [ll.IntType(8).as_pointer().as_pointer()]) + llty = ll.FunctionType(ll.IntType(32), [ll.IntType(8).as_pointer()]) else: assert False @@ -571,7 +571,7 @@ class LLVMIRGenerator: llfun = self.llbuilder.extract_value(llclosure, 1) return llfun, [llenv] + list(llargs) - # See session.c:send_rpc_value and session.c:recv_rpc_value. + # See session.c:{send,receive}_rpc_value and comm_generic.py:_{send,receive}_rpc_value. def _rpc_tag(self, typ, error_handler): if types.is_tuple(typ): assert len(typ.elts) < 256 @@ -666,29 +666,30 @@ class LLVMIRGenerator: llalloc = self.llbuilder.append_basic_block(name=llprehead.name + ".rpc.alloc") lltail = self.llbuilder.append_basic_block(name=llprehead.name + ".rpc.tail") - llslot = self.llbuilder.alloca(ll.IntType(8).as_pointer()) - self.llbuilder.store(ll.Constant(ll.IntType(8).as_pointer(), None), llslot) + llretty = self.llty_of_type(fun_type.ret) + llslot = self.llbuilder.alloca(llretty) + llslotgen = self.llbuilder.bitcast(llslot, ll.IntType(8).as_pointer()) self.llbuilder.branch(llhead) self.llbuilder.position_at_end(llhead) + llphi = self.llbuilder.phi(llslotgen.type) + llphi.add_incoming(llslotgen, llprehead) if llunwindblock: - llsize = self.llbuilder.invoke(self.llbuiltin("recv_rpc"), [llslot], + llsize = self.llbuilder.invoke(self.llbuiltin("recv_rpc"), [llphi], llheadu, llunwindblock) self.llbuilder.position_at_end(llheadu) else: - llsize = self.llbuilder.call(self.llbuiltin("recv_rpc"), [llslot]) + llsize = self.llbuilder.call(self.llbuiltin("recv_rpc"), [llphi]) lldone = self.llbuilder.icmp_unsigned('==', llsize, ll.Constant(llsize.type, 0)) self.llbuilder.cbranch(lldone, lltail, llalloc) self.llbuilder.position_at_end(llalloc) llalloca = self.llbuilder.alloca(ll.IntType(8), llsize) - self.llbuilder.store(llalloca, llslot) + llphi.add_incoming(llalloca, llalloc) self.llbuilder.branch(llhead) self.llbuilder.position_at_end(lltail) - llretty = self.llty_of_type(fun_type.ret, for_return=True) - llretptr = self.llbuilder.bitcast(llslot, llretty.as_pointer()) - llret = self.llbuilder.load(llretptr) + llret = self.llbuilder.load(llslot) if not builtins.is_allocated(fun_type.ret): # We didn't allocate anything except the slot for the value itself. # Don't waste stack space. diff --git a/artiq/coredevice/comm_generic.py b/artiq/coredevice/comm_generic.py index 267708afe..5c604779f 100644 --- a/artiq/coredevice/comm_generic.py +++ b/artiq/coredevice/comm_generic.py @@ -8,7 +8,6 @@ from artiq.language import core as core_language logger = logging.getLogger(__name__) -logger.setLevel(logging.DEBUG) class _H2DMsgType(Enum): @@ -51,6 +50,9 @@ class _D2HMsgType(Enum): class UnsupportedDevice(Exception): pass +class RPCReturnValueError(ValueError): + pass + class CommGeneric: def __init__(self): @@ -279,6 +281,7 @@ class CommGeneric: _rpc_sentinel = object() + # See session.c:{send,receive}_rpc_value and llvm_ir_generator.py:_rpc_tag. def _receive_rpc_value(self, rpc_map): tag = chr(self._read_int8()) if tag == "\x00": @@ -306,11 +309,15 @@ class CommGeneric: length = self._read_int32() return [self._receive_rpc_value(rpc_map) for _ in range(length)] elif tag == "r": - lower = self._receive_rpc_value(rpc_map) - upper = self._receive_rpc_value(rpc_map) + start = self._receive_rpc_value(rpc_map) + stop = self._receive_rpc_value(rpc_map) step = self._receive_rpc_value(rpc_map) - return range(lower, upper, step) + return range(start, stop, step) elif tag == "o": + present = self._read_int8() + if present: + return self._receive_rpc_value(rpc_map) + elif tag == "O": return rpc_map[self._read_int32()] else: raise IOError("Unknown RPC value tag: {}".format(repr(tag))) @@ -323,16 +330,101 @@ class CommGeneric: return args args.append(value) + def _skip_rpc_value(self, tags): + tag = tags.pop(0) + if tag == "t": + length = tags.pop(0) + for _ in range(length): + self._skip_rpc_value(tags) + elif tag == "l": + self._skip_rpc_value(tags) + elif tag == "r": + self._skip_rpc_value(tags) + else: + pass + + def _send_rpc_value(self, tags, value, root, function): + def check(cond, expected): + if not cond: + raise RPCReturnValueError( + "type mismatch: cannot serialize {value} as {type}" + " ({function} has returned {root})".format( + value=repr(value), type=expected(), + function=function, root=root)) + + tag = chr(tags.pop(0)) + if tag == "t": + length = tags.pop(0) + check(isinstance(value, tuple) and length == len(value), + lambda: "tuple of {}".format(length)) + for elt in value: + self._send_rpc_value(tags, elt, root, function) + elif tag == "n": + check(value is None, + lambda: "None") + elif tag == "b": + check(isinstance(value, bool), + lambda: "bool") + self._write_int8(value) + elif tag == "i": + check(isinstance(value, int) and (-2**31 < value < 2**31-1), + lambda: "32-bit int") + self._write_int32(value) + elif tag == "I": + check(isinstance(value, int) and (-2**63 < value < 2**63-1), + lambda: "64-bit int") + self._write_int64(value) + elif tag == "f": + check(isinstance(value, float), + lambda: "float") + self._write_float64(value) + elif tag == "F": + check(isinstance(value, Fraction) and + (-2**63 < value.numerator < 2**63-1) and + (-2**63 < value.denominator < 2**63-1), + lambda: "64-bit Fraction") + self._write_int64(value.numerator) + self._write_int64(value.denominator) + elif tag == "s": + check(isinstance(value, str) and "\x00" not in value, + lambda: "str") + self._write_string(value) + elif tag == "l": + check(isinstance(value, list), + lambda: "list") + self._write_int32(len(value)) + for elt in value: + tags_copy = bytearray(tags) + self._send_rpc_value(tags_copy, elt, root, function) + self._skip_rpc_value(tags) + elif tag == "r": + check(isinstance(value, range), + lambda: "range") + tags_copy = bytearray(tags) + self._send_rpc_value(tags_copy, value.start, root, function) + tags_copy = bytearray(tags) + self._send_rpc_value(tags_copy, value.stop, root, function) + tags_copy = bytearray(tags) + self._send_rpc_value(tags_copy, value.step, root, function) + tags = tags_copy + else: + raise IOError("Unknown RPC value tag: {}".format(repr(tag))) + def _serve_rpc(self, rpc_map): service = self._read_int32() args = self._receive_rpc_args(rpc_map) - return_tag = self._read_string() - logger.debug("rpc service: %d %r -> %s", service, args, return_tag) + return_tags = self._read_bytes() + logger.debug("rpc service: %d %r -> %s", service, args, return_tags) try: result = rpc_map[service](*args) - if not isinstance(result, int) or not (-2**31 < result < 2**31-1): - raise ValueError("An RPC must return an int(width=32)") + logger.debug("rpc service: %d %r == %r", service, args, result) + + self._write_header(_H2DMsgType.RPC_REPLY) + self._write_bytes(return_tags) + self._send_rpc_value(bytearray(return_tags), result, result, + rpc_map[service]) + self._write_flush() except core_language.ARTIQException as exn: logger.debug("rpc service: %d %r ! %r", service, args, exn) @@ -364,12 +456,6 @@ class CommGeneric: self._write_string(function) self._write_flush() - else: - logger.debug("rpc service: %d %r == %r", service, args, result) - - self._write_header(_H2DMsgType.RPC_REPLY) - self._write_int32(result) - self._write_flush() def _serve_exception(self): name = self._read_string() diff --git a/soc/runtime/ksupport.c b/soc/runtime/ksupport.c index 7a681bc59..8abee3969 100644 --- a/soc/runtime/ksupport.c +++ b/soc/runtime/ksupport.c @@ -314,7 +314,7 @@ void send_rpc(int service, const char *tag, ...) va_end(request.args); } -int recv_rpc(void **slot) { +int recv_rpc(void *slot) { struct msg_rpc_recv_request request; struct msg_rpc_recv_reply *reply; diff --git a/soc/runtime/ksupport.h b/soc/runtime/ksupport.h index 2171324a4..88dc7e2a0 100644 --- a/soc/runtime/ksupport.h +++ b/soc/runtime/ksupport.h @@ -6,7 +6,7 @@ void now_save(long long int now); int watchdog_set(int ms); void watchdog_clear(int id); void send_rpc(int service, const char *tag, ...); -int recv_rpc(void **slot); +int recv_rpc(void *slot); void lognonl(const char *fmt, ...); void log(const char *fmt, ...); diff --git a/soc/runtime/messages.h b/soc/runtime/messages.h index 533f296ca..3914cdff6 100644 --- a/soc/runtime/messages.h +++ b/soc/runtime/messages.h @@ -89,7 +89,7 @@ struct msg_rpc_send { struct msg_rpc_recv_request { int type; - void **slot; + void *slot; }; struct msg_rpc_recv_reply { diff --git a/soc/runtime/session.c b/soc/runtime/session.c index 84e04265c..88bcfaefa 100644 --- a/soc/runtime/session.c +++ b/soc/runtime/session.c @@ -147,7 +147,7 @@ static const char *in_packet_string() { int length; const char *string = in_packet_bytes(&length); - if(string[length] != 0) { + if(string[length - 1] != 0) { log("session.c: string is not zero-terminated"); return ""; } @@ -346,6 +346,8 @@ enum { REMOTEMSG_TYPE_FLASH_ERROR_REPLY }; +static int receive_rpc_value(const char **tag, void **slot); + static int process_input(void) { switch(buffer_in.header.type) { @@ -457,23 +459,37 @@ static int process_input(void) user_kernel_state = USER_KERNEL_RUNNING; break; - // case REMOTEMSG_TYPE_RPC_REPLY: { - // struct msg_rpc_reply reply; + case REMOTEMSG_TYPE_RPC_REPLY: { + struct msg_rpc_recv_request *request; + struct msg_rpc_recv_reply reply; - // int result = in_packet_int32(); + if(user_kernel_state != USER_KERNEL_WAIT_RPC) { + log("Unsolicited RPC reply"); + return 0; // restart session + } - // if(user_kernel_state != USER_KERNEL_WAIT_RPC) { - // log("Unsolicited RPC reply"); - // return 0; // restart session - // } + request = mailbox_wait_and_receive(); + if(request->type != MESSAGE_TYPE_RPC_RECV_REQUEST) { + log("Expected MESSAGE_TYPE_RPC_RECV_REQUEST, got %d", + request->type); + return 0; // restart session + } - // reply.type = MESSAGE_TYPE_RPC_REPLY; - // reply.result = result; - // mailbox_send_and_wait(&reply); + const char *tag = in_packet_string(); + void *slot = request->slot; + if(!receive_rpc_value(&tag, &slot)) { + log("Failed to receive RPC reply"); + return 0; // restart session + } - // user_kernel_state = USER_KERNEL_RUNNING; - // break; - // } + reply.type = MESSAGE_TYPE_RPC_RECV_REPLY; + reply.alloc_size = 0; + reply.exception = NULL; + mailbox_send_and_wait(&reply); + + user_kernel_state = USER_KERNEL_RUNNING; + break; + } case REMOTEMSG_TYPE_RPC_EXCEPTION: { struct msg_rpc_recv_request *request; @@ -512,13 +528,191 @@ static int process_input(void) } default: + log("Received invalid packet type %d from host", + buffer_in.header.type); + return 0; + } + + return 1; +} + +// See comm_generic.py:_{send,receive}_rpc_value and llvm_ir_generator.py:_rpc_tag. +static void skip_rpc_value(const char **tag) { + switch(*(*tag)++) { + case 't': { + int size = *(*tag)++; + for(int i = 0; i < size; i++) + skip_rpc_value(tag); + break; + } + + case 'l': + skip_rpc_value(tag); + break; + + case 'r': + skip_rpc_value(tag); + break; + } +} + +static int sizeof_rpc_value(const char **tag) +{ + switch(*(*tag)++) { + case 't': { // tuple + int size = *(*tag)++; + + int32_t length = 0; + for(int i = 0; i < size; i++) + length += sizeof_rpc_value(tag); + return length; + } + + case 'n': // None + return 0; + + case 'b': // bool + return sizeof(int8_t); + + case 'i': // int(width=32) + return sizeof(int32_t); + + case 'I': // int(width=64) + return sizeof(int64_t); + + case 'f': // float + return sizeof(double); + + case 'F': // Fraction + return sizeof(struct { int64_t numerator, denominator; }); + + case 's': // string + return sizeof(char *); + + case 'l': // list(elt='a) + skip_rpc_value(tag); + return sizeof(struct { int32_t length; struct {} *elements; }); + + case 'r': // range(elt='a) + return sizeof_rpc_value(tag) * 3; + + default: + log("sizeof_rpc_value: unknown tag %02x", *((*tag) - 1)); + return 0; + } +} + +static void *alloc_rpc_value(int size) +{ + struct msg_rpc_recv_request *request; + struct msg_rpc_recv_reply reply; + + reply.type = MESSAGE_TYPE_RPC_RECV_REPLY; + reply.alloc_size = size; + reply.exception = NULL; + mailbox_send_and_wait(&reply); + + request = mailbox_wait_and_receive(); + if(request->type != MESSAGE_TYPE_RPC_RECV_REQUEST) { + log("Expected MESSAGE_TYPE_RPC_RECV_REQUEST, got %d", + request->type); + return NULL; + } + return request->slot; +} + +static int receive_rpc_value(const char **tag, void **slot) +{ + switch(*(*tag)++) { + case 't': { // tuple + int size = *(*tag)++; + + for(int i = 0; i < size; i++) { + if(!receive_rpc_value(tag, slot)) + return 0; + } + break; + } + + case 'n': // None + break; + + case 'b': { // bool + *((*(int8_t**)slot)++) = in_packet_int8(); + break; + } + + case 'i': { // int(width=32) + *((*(int32_t**)slot)++) = in_packet_int32(); + break; + } + + case 'I': { // int(width=64) + *((*(int64_t**)slot)++) = in_packet_int64(); + break; + } + + case 'f': { // float + *((*(int64_t**)slot)++) = in_packet_int64(); + break; + } + + case 'F': { // Fraction + struct { int64_t numerator, denominator; } *fraction = *slot; + fraction->numerator = in_packet_int64(); + fraction->denominator = in_packet_int64(); + *slot = (void*)((intptr_t)(*slot) + sizeof(*fraction)); + break; + } + + case 's': { // string + const char *in_string = in_packet_string(); + char *out_string = alloc_rpc_value(strlen(in_string) + 1); + memcpy(out_string, in_string, strlen(in_string) + 1); + *((*(char***)slot)++) = out_string; + break; + } + + case 'l': { // list(elt='a) + struct { int32_t length; struct {} *elements; } *list = *slot; + list->length = in_packet_int32(); + + const char *tag_copy = *tag; + list->elements = alloc_rpc_value(sizeof_rpc_value(&tag_copy) * list->length); + + void *element = list->elements; + for(int i = 0; i < list->length; i++) { + const char *tag_copy = *tag; + if(!receive_rpc_value(&tag_copy, &element)) + return 0; + } + skip_rpc_value(tag); + break; + } + + case 'r': { // range(elt='a) + const char *tag_copy; + tag_copy = *tag; + if(!receive_rpc_value(&tag_copy, slot)) // min + return 0; + tag_copy = *tag; + if(!receive_rpc_value(&tag_copy, slot)) // max + return 0; + tag_copy = *tag; + if(!receive_rpc_value(&tag_copy, slot)) // step + return 0; + *tag = tag_copy; + break; + } + + default: + log("receive_rpc_value: unknown tag %02x", *((*tag) - 1)); return 0; } return 1; } -// See llvm_ir_generator.py:_rpc_tag. static int send_rpc_value(const char **tag, void **value) { if(!out_packet_int8(**tag)) @@ -541,51 +735,33 @@ static int send_rpc_value(const char **tag, void **value) break; case 'b': { // bool - int size = sizeof(int8_t); - if(!out_packet_chunk(*value, size)) - return 0; - *value = (void*)((intptr_t)(*value) + size); - break; + return out_packet_int8(*((*(int8_t**)value)++)); } case 'i': { // int(width=32) - int size = sizeof(int32_t); - if(!out_packet_chunk(*value, size)) - return 0; - *value = (void*)((intptr_t)(*value) + size); - break; + return out_packet_int32(*((*(int32_t**)value)++)); } case 'I': { // int(width=64) - int size = sizeof(int64_t); - if(!out_packet_chunk(*value, size)) - return 0; - *value = (void*)((intptr_t)(*value) + size); - break; + return out_packet_int64(*((*(int64_t**)value)++)); } case 'f': { // float - int size = sizeof(double); - if(!out_packet_chunk(*value, size)) - return 0; - *value = (void*)((intptr_t)(*value) + size); - break; + return out_packet_float64(*((*(double**)value)++)); } case 'F': { // Fraction - int size = sizeof(int64_t) * 2; - if(!out_packet_chunk(*value, size)) + struct { int64_t numerator, denominator; } *fraction = *value; + if(!out_packet_int64(fraction->numerator)) return 0; - *value = (void*)((intptr_t)(*value) + size); + if(!out_packet_int64(fraction->denominator)) + return 0; + *value = (void*)((intptr_t)(*value) + sizeof(*fraction)); break; } case 's': { // string - const char **string = *value; - if(!out_packet_string(*string)) - return 0; - *value = (void*)((intptr_t)(*value) + strlen(*string) + 1); - break; + return out_packet_string(*((*(const char***)value)++)); } case 'l': { // list(elt='a) @@ -595,11 +771,11 @@ static int send_rpc_value(const char **tag, void **value) if(!out_packet_int32(list->length)) return 0; - const char *tag_copy; + const char *tag_copy = *tag; for(int i = 0; i < list->length; i++) { - tag_copy = *tag; if(!send_rpc_value(&tag_copy, &element)) return 0; + tag_copy = *tag; } *tag = tag_copy; @@ -634,7 +810,7 @@ static int send_rpc_value(const char **tag, void **value) if(option->present) { return send_rpc_value(tag, &contents); } else { - (*tag)++; + skip_rpc_value(tag); break; } } @@ -668,8 +844,7 @@ static int send_rpc_request(int service, const char *tag, va_list args) } out_packet_int8(0); - out_packet_string(tag + 1); - + out_packet_string(tag + 1); // return tags out_packet_finish(); return 1; } From f7b64db8f48d968f60f2d9efde3a89f078bade04 Mon Sep 17 00:00:00 2001 From: whitequark Date: Sun, 9 Aug 2015 20:24:16 +0300 Subject: [PATCH 202/369] LLVMIRGenerator: fixup phis on expansion of ARTIQ instructions. --- artiq/compiler/transforms/llvm_ir_generator.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/artiq/compiler/transforms/llvm_ir_generator.py b/artiq/compiler/transforms/llvm_ir_generator.py index 3667e6474..2daba99ef 100644 --- a/artiq/compiler/transforms/llvm_ir_generator.py +++ b/artiq/compiler/transforms/llvm_ir_generator.py @@ -17,6 +17,7 @@ class LLVMIRGenerator: self.llmodule.data_layout = target.data_layout self.llfunction = None self.llmap = {} + self.llblock_map = {} self.fixups = [] def llty_of_type(self, typ, bare=False, for_return=False): @@ -229,6 +230,13 @@ class LLVMIRGenerator: 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 will + # use a different map. + self.llblock_map[block] = self.llbuilder.basic_block + # Fourth, fixup phis. for fixup in self.fixups: fixup() @@ -241,7 +249,7 @@ class LLVMIRGenerator: llinsn = self.llbuilder.phi(self.llty_of_type(insn.type), name=insn.name) def fixup(): for value, block in insn.incoming(): - llinsn.add_incoming(self.map(value), self.map(block)) + llinsn.add_incoming(self.map(value), self.llblock_map[block]) self.fixups.append(fixup) return llinsn From dfc91a35f255d5aef954676f4b43e4a31faa7ec9 Mon Sep 17 00:00:00 2001 From: whitequark Date: Sun, 9 Aug 2015 20:27:04 +0300 Subject: [PATCH 203/369] ARTIQIRGenerator.polymorphic_print: str([x]) uses repr(x), not str(x). --- artiq/compiler/transforms/artiq_ir_generator.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/artiq/compiler/transforms/artiq_ir_generator.py b/artiq/compiler/transforms/artiq_ir_generator.py index 2467bdc6c..f30250d92 100644 --- a/artiq/compiler/transforms/artiq_ir_generator.py +++ b/artiq/compiler/transforms/artiq_ir_generator.py @@ -1489,7 +1489,7 @@ class ARTIQIRGenerator(algorithm.Visitor): tail = self.current_block = self.add_block() self.append(ir.BranchIf(cond, tail, if_failed), block=head) - def polymorphic_print(self, values, separator, suffix=""): + def polymorphic_print(self, values, separator, suffix="", as_repr=False): format_string = "" args = [] def flush(): @@ -1508,7 +1508,7 @@ class ARTIQIRGenerator(algorithm.Visitor): format_string += "("; flush() self.polymorphic_print([self.append(ir.GetAttr(value, index)) for index in range(len(value.type.elts))], - separator=", ") + separator=", ", as_repr=True) if len(value.type.elts) == 1: format_string += ",)" else: @@ -1538,7 +1538,10 @@ class ARTIQIRGenerator(algorithm.Visitor): format_string += "%g" args.append(value) elif builtins.is_str(value.type): - format_string += "%s" + if as_repr: + format_string += "\"%s\"" + else: + format_string += "%s" args.append(value) elif builtins.is_list(value.type): format_string += "["; flush() @@ -1547,7 +1550,7 @@ class ARTIQIRGenerator(algorithm.Visitor): 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="") + self.polymorphic_print([elt], separator="", as_repr=True) is_last = self.append(ir.Compare(ast.Lt(loc=None), index, last)) head = self.current_block From b99eae6b3f4dd5edd2d5419f4dc0534e32aaa0e2 Mon Sep 17 00:00:00 2001 From: whitequark Date: Sun, 9 Aug 2015 20:36:14 +0300 Subject: [PATCH 204/369] session.c: send_rpc_value: fix list serialization. --- soc/runtime/session.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/soc/runtime/session.c b/soc/runtime/session.c index 88bcfaefa..2ea0c0e8e 100644 --- a/soc/runtime/session.c +++ b/soc/runtime/session.c @@ -771,13 +771,12 @@ static int send_rpc_value(const char **tag, void **value) if(!out_packet_int32(list->length)) return 0; - const char *tag_copy = *tag; for(int i = 0; i < list->length; i++) { + const char *tag_copy = *tag; if(!send_rpc_value(&tag_copy, &element)) return 0; - tag_copy = *tag; } - *tag = tag_copy; + skip_rpc_value(tag); *value = (void*)((intptr_t)(*value) + sizeof(*list)); break; From 22570afbdabe66e4867a5de613746a0c830f813a Mon Sep 17 00:00:00 2001 From: whitequark Date: Mon, 10 Aug 2015 09:12:34 +0300 Subject: [PATCH 205/369] LLVMIRGenerator: allocate less. --- .../compiler/transforms/llvm_ir_generator.py | 92 ++++++++++--------- 1 file changed, 51 insertions(+), 41 deletions(-) diff --git a/artiq/compiler/transforms/llvm_ir_generator.py b/artiq/compiler/transforms/llvm_ir_generator.py index 2daba99ef..7e78312f8 100644 --- a/artiq/compiler/transforms/llvm_ir_generator.py +++ b/artiq/compiler/transforms/llvm_ir_generator.py @@ -7,6 +7,16 @@ from pythonparser import ast, diagnostic from llvmlite_artiq import ir as ll from .. import types, builtins, ir + +llvoid = ll.VoidType() +lli1 = ll.IntType(1) +lli8 = ll.IntType(8) +lli32 = ll.IntType(32) +lldouble = ll.DoubleType() +llptr = ll.IntType(8).as_pointer() +llemptyptr = ll.LiteralStructType( []).as_pointer() + + class LLVMIRGenerator: def __init__(self, engine, module_name, target): self.engine = engine @@ -26,11 +36,11 @@ class LLVMIRGenerator: return ll.LiteralStructType([self.llty_of_type(eltty) for eltty in typ.elts]) elif types.is_rpc_function(typ): if for_return: - return ll.VoidType() + return llvoid else: return ll.LiteralStructType([]) elif types.is_function(typ): - envarg = ll.IntType(8).as_pointer() + envarg = llptr llty = ll.FunctionType(args=[envarg] + [self.llty_of_type(typ.args[arg]) for arg in typ.args] + @@ -43,27 +53,27 @@ class LLVMIRGenerator: return ll.LiteralStructType([envarg, llty.as_pointer()]) elif builtins.is_none(typ): if for_return: - return ll.VoidType() + return llvoid else: return ll.LiteralStructType([]) elif builtins.is_bool(typ): - return ll.IntType(1) + return lli1 elif builtins.is_int(typ): return ll.IntType(builtins.get_int_width(typ)) elif builtins.is_float(typ): - return ll.DoubleType() + return lldouble elif builtins.is_str(typ) or ir.is_exn_typeinfo(typ): - return ll.IntType(8).as_pointer() + return llptr elif builtins.is_list(typ): lleltty = self.llty_of_type(builtins.get_iterable_elt(typ)) - return ll.LiteralStructType([ll.IntType(32), lleltty.as_pointer()]) + return ll.LiteralStructType([lli32, lleltty.as_pointer()]) 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 ll.IntType(8).as_pointer() + return llptr elif ir.is_option(typ): - return ll.LiteralStructType([ll.IntType(1), self.llty_of_type(typ.params["inner"])]) + return ll.LiteralStructType([lli1, self.llty_of_type(typ.params["inner"])]) elif ir.is_environment(typ): llty = ll.LiteralStructType([self.llty_of_type(typ.params[name]) for name in typ.params]) @@ -117,14 +127,14 @@ class LLVMIRGenerator: llconst = self.llmodule.get_global(name) if llconst is None: - llstrty = ll.ArrayType(ll.IntType(8), len(as_bytes)) + llstrty = ll.ArrayType(lli8, len(as_bytes)) llconst = ll.GlobalVariable(self.llmodule, llstrty, name) llconst.global_constant = True llconst.initializer = ll.Constant(llstrty, bytearray(as_bytes)) llconst.linkage = linkage llconst.unnamed_addr = unnamed_addr - return llconst.bitcast(ll.IntType(8).as_pointer()) + return llconst.bitcast(llptr) else: assert False @@ -134,36 +144,36 @@ class LLVMIRGenerator: return llfun if name in "llvm.donothing": - llty = ll.FunctionType(ll.VoidType(), []) + llty = ll.FunctionType(llvoid, []) elif name in "llvm.trap": - llty = ll.FunctionType(ll.VoidType(), []) + llty = ll.FunctionType(llvoid, []) elif name == "llvm.floor.f64": - llty = ll.FunctionType(ll.DoubleType(), [ll.DoubleType()]) + llty = ll.FunctionType(lldouble, [lldouble]) elif name == "llvm.round.f64": - llty = ll.FunctionType(ll.DoubleType(), [ll.DoubleType()]) + llty = ll.FunctionType(lldouble, [lldouble]) elif name == "llvm.pow.f64": - llty = ll.FunctionType(ll.DoubleType(), [ll.DoubleType(), ll.DoubleType()]) + llty = ll.FunctionType(lldouble, [lldouble, lldouble]) elif name == "llvm.powi.f64": - llty = ll.FunctionType(ll.DoubleType(), [ll.DoubleType(), ll.IntType(32)]) + llty = ll.FunctionType(lldouble, [lldouble, lli32]) elif name == "llvm.copysign.f64": - llty = ll.FunctionType(ll.DoubleType(), [ll.DoubleType(), ll.DoubleType()]) + llty = ll.FunctionType(lldouble, [lldouble, lldouble]) elif name == "llvm.stacksave": - llty = ll.FunctionType(ll.IntType(8).as_pointer(), []) + llty = ll.FunctionType(llptr, []) elif name == "llvm.stackrestore": - llty = ll.FunctionType(ll.VoidType(), [ll.IntType(8).as_pointer()]) + llty = ll.FunctionType(llvoid, [llptr]) elif name == self.target.print_function: - llty = ll.FunctionType(ll.VoidType(), [ll.IntType(8).as_pointer()], var_arg=True) + llty = ll.FunctionType(llvoid, [llptr], var_arg=True) elif name == "__artiq_personality": - llty = ll.FunctionType(ll.IntType(32), [], var_arg=True) + llty = ll.FunctionType(lli32, [], var_arg=True) elif name == "__artiq_raise": - llty = ll.FunctionType(ll.VoidType(), [self.llty_of_type(builtins.TException())]) + llty = ll.FunctionType(llvoid, [self.llty_of_type(builtins.TException())]) elif name == "__artiq_reraise": - llty = ll.FunctionType(ll.VoidType(), []) + llty = ll.FunctionType(llvoid, []) elif name == "send_rpc": - llty = ll.FunctionType(ll.VoidType(), [ll.IntType(32), ll.IntType(8).as_pointer()], + llty = ll.FunctionType(llvoid, [lli32, llptr], var_arg=True) elif name == "recv_rpc": - llty = ll.FunctionType(ll.IntType(32), [ll.IntType(8).as_pointer()]) + llty = ll.FunctionType(lli32, [llptr]) else: assert False @@ -254,7 +264,7 @@ class LLVMIRGenerator: return llinsn def llindex(self, index): - return ll.Constant(ll.IntType(32), index) + return ll.Constant(lli32, index) def process_Alloc(self, insn): if ir.is_environment(insn.type): @@ -263,11 +273,11 @@ class LLVMIRGenerator: 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(ll.IntType(1), False), 0, + 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(ll.IntType(1), True), 0) + 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: @@ -439,8 +449,8 @@ class LLVMIRGenerator: [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)) + 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) @@ -497,19 +507,19 @@ class LLVMIRGenerator: 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)): + elif isinstance(lllhs.type, 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) + 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(ll.IntType(1), False)) - return self.llbuilder.icmp_unsigned(op, llvalue, ll.Constant(ll.IntType(1), True), + ll.Constant(lli1, False)) + return self.llbuilder.icmp_unsigned(op, llvalue, ll.Constant(lli1, True), name=insn.name) else: print(lllhs, llrhs) @@ -567,7 +577,7 @@ class LLVMIRGenerator: def process_Closure(self, insn): llvalue = ll.Constant(self.llty_of_type(insn.target_function.type), ll.Undefined) - llenv = self.llbuilder.bitcast(self.map(insn.environment()), ll.IntType(8).as_pointer()) + llenv = self.llbuilder.bitcast(self.map(insn.environment()), llptr) llvalue = self.llbuilder.insert_value(llvalue, llenv, 0) llvalue = self.llbuilder.insert_value(llvalue, self.map(insn.target_function), 1, name=insn.name) @@ -611,7 +621,7 @@ class LLVMIRGenerator: error_handler(typ) def _build_rpc(self, fun_loc, fun_type, args, llnormalblock, llunwindblock): - llservice = ll.Constant(ll.IntType(32), fun_type.service) + llservice = ll.Constant(lli32, fun_type.service) tag = b"" @@ -676,7 +686,7 @@ class LLVMIRGenerator: llretty = self.llty_of_type(fun_type.ret) llslot = self.llbuilder.alloca(llretty) - llslotgen = self.llbuilder.bitcast(llslot, ll.IntType(8).as_pointer()) + llslotgen = self.llbuilder.bitcast(llslot, llptr) self.llbuilder.branch(llhead) self.llbuilder.position_at_end(llhead) @@ -692,7 +702,7 @@ class LLVMIRGenerator: self.llbuilder.cbranch(lldone, lltail, llalloc) self.llbuilder.position_at_end(llalloc) - llalloca = self.llbuilder.alloca(ll.IntType(8), llsize) + llalloca = self.llbuilder.alloca(lli8, llsize) llphi.add_incoming(llalloca, llalloc) self.llbuilder.branch(llhead) @@ -783,8 +793,8 @@ class LLVMIRGenerator: def process_LandingPad(self, insn): # Layout on return from landing pad: {%_Unwind_Exception*, %Exception*} - lllandingpadty = ll.LiteralStructType([ll.IntType(8).as_pointer(), - ll.IntType(8).as_pointer()]) + lllandingpadty = ll.LiteralStructType([llptr, + llptr]) lllandingpad = self.llbuilder.landingpad(lllandingpadty, self.llbuiltin("__artiq_personality"), cleanup=True) From 8f510a440706f93091b92a5cc8039627ab2694d9 Mon Sep 17 00:00:00 2001 From: whitequark Date: Mon, 10 Aug 2015 13:14:52 +0300 Subject: [PATCH 206/369] compiler.ir.Function: add loc field. --- artiq/compiler/embedding.py | 3 ++- artiq/compiler/ir.py | 9 +++++++-- artiq/compiler/transforms/artiq_ir_generator.py | 6 ++++-- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/artiq/compiler/embedding.py b/artiq/compiler/embedding.py index bf1ed49d0..3dacbdc13 100644 --- a/artiq/compiler/embedding.py +++ b/artiq/compiler/embedding.py @@ -184,9 +184,10 @@ class Stitcher: inferencer.visit(self.typedtree) # 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=None) + body=self.typedtree, loc=source.Range(source_buffer, 0, 0)) def _quote_embedded_function(self, function): if not hasattr(function, "artiq_embedded"): diff --git a/artiq/compiler/ir.py b/artiq/compiler/ir.py index 327e8c5f6..2561d4ea4 100644 --- a/artiq/compiler/ir.py +++ b/artiq/compiler/ir.py @@ -138,6 +138,9 @@ class User(NamedValue): class Instruction(User): """ An SSA instruction. + + :ivar loc: (:class:`pythonparser.source.Range` or None) + source location """ def __init__(self, operands, typ, name=""): @@ -388,13 +391,15 @@ 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 """ - def __init__(self, typ, name, arguments): - self.type, self.name = typ, name + 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) diff --git a/artiq/compiler/transforms/artiq_ir_generator.py b/artiq/compiler/transforms/artiq_ir_generator.py index f30250d92..b0ce78c91 100644 --- a/artiq/compiler/transforms/artiq_ir_generator.py +++ b/artiq/compiler/transforms/artiq_ir_generator.py @@ -129,7 +129,8 @@ class ARTIQIRGenerator(algorithm.Visitor): try: typ = types.TFunction(OrderedDict(), OrderedDict(), builtins.TNone()) - func = ir.Function(typ, ".".join(self.name + ['__modinit__']), []) + 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 @@ -184,7 +185,8 @@ class ARTIQIRGenerator(algorithm.Visitor): for arg_name in typ.optargs: optargs.append(ir.Argument(ir.TOption(typ.optargs[arg_name]), "arg." + arg_name)) - func = ir.Function(typ, ".".join(self.name), [env_arg] + args + optargs) + func = ir.Function(typ, ".".join(self.name), [env_arg] + args + optargs, + loc=node.keyword_loc) func.is_internal = is_internal self.functions.append(func) old_func, self.current_function = self.current_function, func From 4f02f6e667be05606e52516d7c6f8835ba4135d0 Mon Sep 17 00:00:00 2001 From: whitequark Date: Mon, 10 Aug 2015 13:15:42 +0300 Subject: [PATCH 207/369] compiler.types: make all hashable. --- artiq/compiler/types.py | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/artiq/compiler/types.py b/artiq/compiler/types.py index 4c145720c..a43a235ef 100644 --- a/artiq/compiler/types.py +++ b/artiq/compiler/types.py @@ -6,6 +6,12 @@ in :mod:`asttyped`. import string from collections import OrderedDict + +class UnificationError(Exception): + def __init__(self, typea, typeb): + self.typea, self.typeb = typea, typeb + + def genalnum(): ident = ["a"] while True: @@ -22,9 +28,8 @@ def genalnum(): if pos < 0: ident = ["a"] + ident -class UnificationError(Exception): - def __init__(self, typea, typeb): - self.typea, self.typeb = typea, typeb +def _freeze(dict_): + return tuple((key, dict_[key]) for key in dict_) def _map_find(elts): if isinstance(elts, list): @@ -34,6 +39,7 @@ def _map_find(elts): else: assert False + class Type(object): pass @@ -93,6 +99,7 @@ class TMono(Type): attributes = OrderedDict() def __init__(self, name, params={}): + assert isinstance(params, dict) self.name, self.params = name, params def find(self): @@ -127,6 +134,9 @@ class TMono(Type): def __ne__(self, other): return not (self == other) + def __hash__(self): + return hash((self.name, _freeze(self.params))) + class TTuple(Type): """ A tuple type. @@ -181,6 +191,9 @@ class TFunction(Type): attributes = OrderedDict() 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 def arity(self): @@ -222,6 +235,9 @@ class TFunction(Type): def __ne__(self, other): return not (self == other) + def __hash__(self): + return hash((_freeze(self.args), _freeze(self.optargs), self.ret)) + class TRPCFunction(TFunction): """ A function type of a remote function. @@ -249,6 +265,7 @@ class TBuiltin(Type): """ def __init__(self, name): + assert isinstance(name, str) self.name = name self.attributes = OrderedDict() From c63ec70c53a60630424cb31362c22ca07c27ff7a Mon Sep 17 00:00:00 2001 From: whitequark Date: Mon, 10 Aug 2015 15:11:52 +0300 Subject: [PATCH 208/369] LLVMIRGenerator: emit debug information. --- .../compiler/transforms/llvm_ir_generator.py | 180 ++++++++++++++++-- 1 file changed, 163 insertions(+), 17 deletions(-) diff --git a/artiq/compiler/transforms/llvm_ir_generator.py b/artiq/compiler/transforms/llvm_ir_generator.py index 7e78312f8..8bda72f5d 100644 --- a/artiq/compiler/transforms/llvm_ir_generator.py +++ b/artiq/compiler/transforms/llvm_ir_generator.py @@ -3,6 +3,7 @@ into LLVM intermediate representation. """ +import os from pythonparser import ast, diagnostic from llvmlite_artiq import ir as ll from .. import types, builtins, ir @@ -14,7 +15,147 @@ lli8 = ll.IntType(8) lli32 = ll.IntType(32) lldouble = ll.DoubleType() llptr = ll.IntType(8).as_pointer() -llemptyptr = ll.LiteralStructType( []).as_pointer() +llmetadata = ll.MetaData() + + +DW_LANG_Python = 0x0014 +DW_TAG_compile_unit = 17 +DW_TAG_subroutine_type = 21 +DW_TAG_file_type = 41 +DW_TAG_subprogram = 46 + +def memoize(generator): + def memoized(self, *args): + result = self.cache.get((generator,) + args, None) + if result is None: + return generator(self, *args) + else: + return result + return memoized + +class DebugInfoEmitter: + def __init__(self, llmodule): + self.llmodule = llmodule + self.cache = {} + self.subprograms = [] + + def emit(self, operands): + def map_operand(operand): + if operand is None: + return ll.Constant(llmetadata, None) + elif isinstance(operand, str): + return ll.MetaDataString(self.llmodule, operand) + elif isinstance(operand, bool): + return ll.Constant(lli1, operand) + elif isinstance(operand, int): + return ll.Constant(lli32, operand) + elif isinstance(operand, (list, tuple)): + return self.emit(operand) + elif isinstance(operand, ll.Value): + return operand + else: + print(operand) + assert False + return self.llmodule.add_metadata(list(map(map_operand, operands))) + + @memoize + def emit_filename(self, source_buffer): + source_dir, source_file = os.path.split(source_buffer.name) + return self.emit([source_file, source_dir]) + + @memoize + def emit_compile_unit(self, source_buffer, llsubprograms): + return self.emit([ + DW_TAG_compile_unit, + self.emit_filename(source_buffer), # filename + DW_LANG_Python, # source language + "ARTIQ", # producer + False, # optimized? + "", # linker flags + 0, # runtime version + [], # enum types + [], # retained types + llsubprograms, # subprograms + [], # global variables + [], # imported entities + "", # split debug filename + 2, # kind (full=1, lines only=2) + ]) + + @memoize + def emit_file(self, source_buffer): + return self.emit([ + DW_TAG_file_type, + self.emit_filename(source_buffer), # filename + ]) + + @memoize + def emit_subroutine_type(self, typ): + return self.emit([ + DW_TAG_subroutine_type, + None, # filename + None, # context descriptor + "", # name + 0, # line number + 0, # (i64) size in bits + 0, # (i64) alignment in bits + 0, # (i64) offset in bits + 0, # flags + None, # derived from + [None], # members + 0, # runtime languages + None, # base type with vtable pointer + None, # template parameters + None # unique identifier + ]) + + @memoize + def emit_subprogram(self, func, llfunc): + source_buffer = func.loc.source_buffer + display_name = "{}{}".format(func.name, types.TypePrinter().name(func.type)) + subprogram = self.emit([ + DW_TAG_subprogram, + self.emit_filename(source_buffer), # filename + self.emit_file(source_buffer), # context descriptor + func.name, # name + display_name, # display name + llfunc.name, # linkage name + func.loc.line(), # line number where defined + self.emit_subroutine_type(func.type), # type descriptor + func.is_internal, # local to compile unit? + True, # global is defined in the compile unit? + 0, # virtuality + 0, # index into a virtual function + None, # base type with vtable pointer + 0, # flags + False, # optimized? + llfunc, # LLVM function + None, # template parameters + None, # function declaration descriptor + [], # function variables + func.loc.line(), # line number where scope begins + ]) + self.subprograms.append(subprogram) + return subprogram + + @memoize + def emit_loc(self, loc, scope, inlined_scope=None): + return self.emit([ + loc.line(), # line + loc.column(), # column + scope, # scope + inlined_scope, # inlined scope + ]) + + def finalize(self, source_buffer): + llident = self.llmodule.add_named_metadata('llvm.ident') + llident.add(self.emit(["ARTIQ"])) + + llflags = self.llmodule.add_named_metadata('llvm.module.flags') + llflags.add(self.emit([2, "Debug Info Version", 1])) + + llcompile_units = self.llmodule.add_named_metadata('llvm.dbg.cu') + llcompile_units.add(self.emit_compile_unit(source_buffer, tuple(self.subprograms))) class LLVMIRGenerator: @@ -27,8 +168,8 @@ class LLVMIRGenerator: self.llmodule.data_layout = target.data_layout self.llfunction = None self.llmap = {} - self.llblock_map = {} - self.fixups = [] + self.phis = [] + self.debug_info_emitter = DebugInfoEmitter(self.llmodule) def llty_of_type(self, typ, bare=False, for_return=False): typ = typ.find() @@ -200,6 +341,9 @@ class LLVMIRGenerator: for func in functions: self.process_function(func) + if any(functions): + self.debug_info_emitter.finalize(functions[0].loc.source_buffer) + return self.llmodule def process_function(self, func): @@ -219,9 +363,10 @@ class LLVMIRGenerator: self.llfunction.attributes.add('uwtable') - self.llmap = {} self.llbuilder = ll.IRBuilder() - self.fixups = [] + llblock_map = {} + + disubprogram = self.debug_info_emitter.emit_subprogram(func, self.llfunction) # First, map arguments. for arg, llarg in zip(func.arguments, self.llfunction.args): @@ -240,27 +385,29 @@ class LLVMIRGenerator: assert llinsn is not None self.llmap[insn] = llinsn + if insn.loc is not None: + diloc = self.debug_info_emitter.emit_loc(insn.loc, disubprogram) + llinsn.set_metadata('dbg', diloc) + # 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 will # use a different map. - self.llblock_map[block] = self.llbuilder.basic_block + llblock_map[block] = self.llbuilder.basic_block - # Fourth, fixup phis. - for fixup in self.fixups: - fixup() + # 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.llfunction = None - self.llmap = None - self.fixups = [] + self.llmap = {} + self.llphis = [] def process_Phi(self, insn): llinsn = self.llbuilder.phi(self.llty_of_type(insn.type), name=insn.name) - def fixup(): - for value, block in insn.incoming(): - llinsn.add_incoming(self.map(value), self.llblock_map[block]) - self.fixups.append(fixup) + self.phis.append((insn, llinsn)) return llinsn def llindex(self, index): @@ -793,8 +940,7 @@ class LLVMIRGenerator: def process_LandingPad(self, insn): # Layout on return from landing pad: {%_Unwind_Exception*, %Exception*} - lllandingpadty = ll.LiteralStructType([llptr, - llptr]) + lllandingpadty = ll.LiteralStructType([llptr, llptr]) lllandingpad = self.llbuilder.landingpad(lllandingpadty, self.llbuiltin("__artiq_personality"), cleanup=True) From 75532d10aa96ab8d453ae39233b9604e69ffcb65 Mon Sep 17 00:00:00 2001 From: whitequark Date: Mon, 10 Aug 2015 15:12:22 +0300 Subject: [PATCH 209/369] Display full core device backtraces. --- artiq/compiler/targets.py | 104 +++++++++++++++++++++++-------- artiq/coredevice/comm_generic.py | 13 ++-- artiq/coredevice/core.py | 19 +++--- artiq/language/core.py | 39 +++++++----- 4 files changed, 117 insertions(+), 58 deletions(-) diff --git a/artiq/compiler/targets.py b/artiq/compiler/targets.py index a023945f7..2e1ab764c 100644 --- a/artiq/compiler/targets.py +++ b/artiq/compiler/targets.py @@ -5,6 +5,43 @@ llvm.initialize() llvm.initialize_all_targets() llvm.initialize_all_asmprinters() +class RunTool: + def __init__(self, pattern, **tempdata): + self.files = [] + self.pattern = pattern + self.tempdata = tempdata + + def maketemp(self, data): + f = tempfile.NamedTemporaryFile() + f.write(data) + f.flush() + self.files.append(f) + return f + + def __enter__(self): + tempfiles = {} + tempnames = {} + for key in self.tempdata: + tempfiles[key] = self.maketemp(self.tempdata[key]) + tempnames[key] = tempfiles[key].name + + cmdline = [] + for argument in self.pattern: + cmdline.append(argument.format(**tempnames)) + + process = subprocess.Popen(cmdline, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + stdout, stderr = process.communicate() + if process.returncode != 0: + raise Exception("{} invocation failed: {}". + format(cmdline[0], stderr.decode('utf-8'))) + + tempfiles["__stdout__"] = stdout.decode('utf-8') + return tempfiles + + def __exit__(self, exc_typ, exc_value, exc_trace): + for f in self.files: + f.close() + class Target: """ A description of the target environment where the binaries @@ -25,35 +62,10 @@ class Target: features = [] print_function = "printf" + def __init__(self): self.llcontext = ll.Context() - def link(self, objects, init_fn): - """Link the relocatable objects into a shared library for this target.""" - files = [] - - def make_tempfile(data=b""): - f = tempfile.NamedTemporaryFile() - files.append(f) - f.write(data) - f.flush() - return f - - try: - output_file = make_tempfile() - cmdline = [self.triple + "-ld", "-shared", "--eh-frame-hdr", "-init", init_fn] + \ - [make_tempfile(obj).name for obj in objects] + \ - ["-o", output_file.name] - linker = subprocess.Popen(cmdline, stderr=subprocess.PIPE) - stdout, stderr = linker.communicate() - if linker.returncode != 0: - raise Exception("Linker invocation failed: " + stderr.decode('utf-8')) - - return output_file.read() - finally: - for f in files: - f.close() - def compile(self, module): """Compile the module to a relocatable object for this target.""" @@ -93,10 +105,50 @@ class Target: return llmachine.emit_object(llparsedmod) + def link(self, objects, init_fn): + """Link the relocatable objects into a shared library for this target.""" + with RunTool([self.triple + "-ld", "-shared", "--eh-frame-hdr", "-init", init_fn] + + ["{{obj{}}}".format(index) for index in range(len(objects))] + + ["-o", "{output}"], + output=b"", + **{"obj{}".format(index): obj for index, obj in enumerate(objects)}) \ + as results: + library = results["output"].read() + + if os.getenv('ARTIQ_DUMP_ELF'): + shlib_temp = tempfile.NamedTemporaryFile(suffix=".so", delete=False) + shlib_temp.write(library) + shlib_temp.close() + print("====== SHARED LIBRARY DUMP ======", file=sys.stderr) + print("Shared library dumped as {}".format(shlib_temp.name), file=sys.stderr) + + return library + def compile_and_link(self, modules): return self.link([self.compile(module) for module in modules], init_fn=modules[0].entry_point()) + def strip(self, library): + with RunTool([self.triple + "-strip", "--strip-debug", "{library}", "-o", "{output}"], + library=library, output=b"") \ + as results: + return results["output"].read() + + def symbolize(self, library, addresses): + # Addresses point one instruction past the jump; offset them back by 1. + offset_addresses = [hex(addr - 1) for addr in addresses] + with RunTool([self.triple + "-addr2line", "--functions", "--inlines", + "--exe={library}"] + offset_addresses, + library=library) \ + as results: + lines = results["__stdout__"].rstrip().split("\n") + backtrace = [] + for function_name, location, address in zip(lines[::2], lines[1::2], addresses): + filename, line = location.rsplit(":", 1) + # can't get column out of addr2line D: + backtrace.append((filename, int(line), -1, function_name, address)) + return backtrace + class NativeTarget(Target): def __init__(self): super().__init__() diff --git a/artiq/coredevice/comm_generic.py b/artiq/coredevice/comm_generic.py index 5c604779f..3f1a188d3 100644 --- a/artiq/coredevice/comm_generic.py +++ b/artiq/coredevice/comm_generic.py @@ -457,7 +457,7 @@ class CommGeneric: self._write_flush() - def _serve_exception(self): + def _serve_exception(self, symbolizer): name = self._read_string() message = self._read_string() params = [self._read_int64() for _ in range(3)] @@ -468,19 +468,18 @@ class CommGeneric: function = self._read_string() backtrace = [self._read_int32() for _ in range(self._read_int32())] - # we don't have debug information yet. - # print("exception backtrace:", [hex(x) for x in backtrace]) - raise core_language.ARTIQException(name, message, params, - filename, line, column, function) + traceback = list(reversed(symbolizer(backtrace))) + \ + [(filename, line, column, function, None)] + raise core_language.ARTIQException(name, message, params, traceback) - def serve(self, rpc_map): + def serve(self, rpc_map, symbolizer): while True: self._read_header() if self._read_type == _D2HMsgType.RPC_REQUEST: self._serve_rpc(rpc_map) elif self._read_type == _D2HMsgType.KERNEL_EXCEPTION: - self._serve_exception() + self._serve_exception(symbolizer) else: self._read_expect(_D2HMsgType.KERNEL_FINISHED) return diff --git a/artiq/coredevice/core.py b/artiq/coredevice/core.py index 91fa01562..fb54cc9a3 100644 --- a/artiq/coredevice/core.py +++ b/artiq/coredevice/core.py @@ -35,29 +35,26 @@ class Core: module = Module(stitcher) target = OR1KTarget() - return target.compile_and_link([module]), stitcher.rpc_map + library = target.compile_and_link([module]) + stripped_library = target.strip(library) + + return stitcher.rpc_map, stripped_library, \ + lambda addresses: target.symbolize(library, addresses) except diagnostic.Error as error: print("\n".join(error.diagnostic.render(colored=True)), file=sys.stderr) raise CompileError() from error def run(self, function, args, kwargs): - kernel_library, rpc_map = self.compile(function, args, kwargs) + rpc_map, kernel_library, symbolizer = self.compile(function, args, kwargs) if self.first_run: self.comm.check_ident() self.comm.switch_clock(self.external_clock) self.first_run = False - try: - self.comm.load(kernel_library) - except Exception as error: - shlib_temp = tempfile.NamedTemporaryFile(suffix=".so", delete=False) - shlib_temp.write(kernel_library) - shlib_temp.close() - raise RuntimeError("shared library dumped to {}".format(shlib_temp.name)) from error - + self.comm.load(kernel_library) self.comm.run() - self.comm.serve(rpc_map) + self.comm.serve(rpc_map, symbolizer) @kernel def get_rtio_counter_mu(self): diff --git a/artiq/language/core.py b/artiq/language/core.py index 9ada84e9f..b4f547d35 100644 --- a/artiq/language/core.py +++ b/artiq/language/core.py @@ -279,8 +279,7 @@ class ARTIQException(Exception): """Base class for exceptions raised or passed through the core device.""" # Try and create an instance of the specific class, if one exists. - def __new__(cls, name, message, - params, filename, line, column, function): + def __new__(cls, name, message, params, traceback): def find_subclass(cls): if cls.__name__ == name: return cls @@ -295,16 +294,13 @@ class ARTIQException(Exception): more_specific_cls = cls exn = Exception.__new__(more_specific_cls) - exn.__init__(name, message, params, - filename, line, column, function) + exn.__init__(name, message, params, traceback) return exn - def __init__(self, name, message, params, - filename, line, column, function): + def __init__(self, name, message, params, traceback): Exception.__init__(self, name, message, *params) self.name, self.message, self.params = name, message, params - self.filename, self.line, self.column = filename, line, column - self.function = function + self.traceback = list(traceback) def __str__(self): lines = [] @@ -315,11 +311,26 @@ class ARTIQException(Exception): lines.append("({}) {}".format(self.name, self.message.format(*self.params))) lines.append("Core Device Traceback (most recent call last):") - lines.append(" File \"{file}\", line {line}, column {column}, in {function}". - format(file=self.filename, line=self.line, column=self.column + 1, - function=self.function)) - line = linecache.getline(self.filename, self.line) - lines.append(" {}".format(line.strip() if line else "")) - lines.append(" {}^".format(" " * (self.column - re.search(r"^\s+", line).end()))) + for (filename, line, column, function, address) in self.traceback: + source_line = linecache.getline(filename, line) + indentation = re.search(r"^\s*", source_line).end() + + if address is None: + formatted_address = "" + else: + formatted_address = " (RA=0x{:x})".format(address) + + if column == -1: + lines.append(" File \"{file}\", line {line}, in {function}{address}". + format(file=filename, line=line, function=function, + address=formatted_address)) + lines.append(" {}".format(source_line.strip() if source_line else "")) + else: + lines.append(" File \"{file}\", line {line}, column {column}," + " in {function}{address}". + format(file=filename, line=line, column=column + 1, + function=function, address=formatted_address)) + lines.append(" {}".format(source_line.strip() if source_line else "")) + lines.append(" {}^".format(" " * (column - indentation))) return "\n".join(lines) From 261515dfe51c8a9328bd1d838da8aa21658b3576 Mon Sep 17 00:00:00 2001 From: whitequark Date: Mon, 10 Aug 2015 15:47:44 +0300 Subject: [PATCH 210/369] compiler.targets.OR1KTarget: fix typo. --- artiq/compiler/targets.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/artiq/compiler/targets.py b/artiq/compiler/targets.py index 2e1ab764c..1ed440236 100644 --- a/artiq/compiler/targets.py +++ b/artiq/compiler/targets.py @@ -157,5 +157,5 @@ class NativeTarget(Target): class OR1KTarget(Target): triple = "or1k-linux" data_layout = "E-m:e-p:32:32-i64:32-f64:32-v64:32-v128:32-a:0:32-n32" - attributes = ["mul", "div", "ffl1", "cmov", "addc"] + features = ["mul", "div", "ffl1", "cmov", "addc"] print_function = "lognonl" From f53a5ff202d07600d4be0d2d75b71c1e454e78f7 Mon Sep 17 00:00:00 2001 From: whitequark Date: Mon, 10 Aug 2015 16:44:29 +0300 Subject: [PATCH 211/369] Remove syscall builtin. --- artiq/compiler/builtins.py | 3 --- artiq/compiler/prelude.py | 1 - artiq/compiler/transforms/inferencer.py | 4 ---- 3 files changed, 8 deletions(-) diff --git a/artiq/compiler/builtins.py b/artiq/compiler/builtins.py index 4a8280631..c34ccf126 100644 --- a/artiq/compiler/builtins.py +++ b/artiq/compiler/builtins.py @@ -150,9 +150,6 @@ def fn_print(): def fn_kernel(): return types.TBuiltinFunction("kernel") -def fn_syscall(): - return types.TBuiltinFunction("syscall") - # Accessors def is_none(typ): diff --git a/artiq/compiler/prelude.py b/artiq/compiler/prelude.py index 3c58674c6..579a474cf 100644 --- a/artiq/compiler/prelude.py +++ b/artiq/compiler/prelude.py @@ -20,5 +20,4 @@ def globals(): "round": builtins.fn_round(), "print": builtins.fn_print(), "kernel": builtins.fn_kernel(), - "syscall": builtins.fn_syscall(), } diff --git a/artiq/compiler/transforms/inferencer.py b/artiq/compiler/transforms/inferencer.py index 47145b9a9..a8ea47902 100644 --- a/artiq/compiler/transforms/inferencer.py +++ b/artiq/compiler/transforms/inferencer.py @@ -666,10 +666,6 @@ class Inferencer(algorithm.Visitor): pass else: diagnose(valid_forms()) - # TODO: add when it is clear what interface syscall() has - # elif types.is_builtin(typ, "syscall"): - # valid_Forms = lambda: [ - # ] def visit_CallT(self, node): self.generic_visit(node) From b28a8742741705030d251efe02411aa062a08cec Mon Sep 17 00:00:00 2001 From: whitequark Date: Mon, 10 Aug 2015 17:06:55 +0300 Subject: [PATCH 212/369] Inferencer: range() does not accept a float argument. --- artiq/compiler/transforms/inferencer.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/artiq/compiler/transforms/inferencer.py b/artiq/compiler/transforms/inferencer.py index a8ea47902..a256f9775 100644 --- a/artiq/compiler/transforms/inferencer.py +++ b/artiq/compiler/transforms/inferencer.py @@ -596,7 +596,7 @@ class Inferencer(algorithm.Visitor): self._unify(arg.type, range_tvar, arg.loc, None) - if builtins.is_numeric(arg.type): + if builtins.is_int(arg.type): pass elif types.is_var(arg.type): pass # undetermined yet @@ -606,7 +606,7 @@ class Inferencer(algorithm.Visitor): {"type": types.TypePrinter().name(arg.type)}, arg.loc) diag = diagnostic.Diagnostic("error", - "an argument of range() must be of a numeric type", {}, + "an argument of range() must be of an integer type", {}, node.func.loc, notes=[note]) self.engine.process(diag) else: @@ -616,15 +616,16 @@ class Inferencer(algorithm.Visitor): valid_form("len(x:'a) -> int(width='b) where 'a is iterable"), ] - # TODO: should be ssize_t-sized - self._unify(node.type, builtins.TInt(types.TValue(32)), - node.loc, None) - if len(node.args) == 1 and len(node.keywords) == 0: arg, = node.args - if builtins.is_iterable(arg.type): - pass + if builtins.is_range(arg.type): + self._unify(node.type, builtins.get_iterable_elt(arg.type), + node.loc, None) + elif builtins.is_list(arg.type): + # TODO: should be ssize_t-sized + self._unify(node.type, builtins.TInt(types.TValue(32)), + node.loc, None) elif types.is_var(arg.type): pass # undetermined yet else: From 435559fe501a3bc2fd05fa71024dce5e844648e9 Mon Sep 17 00:00:00 2001 From: whitequark Date: Mon, 10 Aug 2015 17:48:35 +0300 Subject: [PATCH 213/369] Allow type annotations on remotely called functions. --- artiq/compiler/embedding.py | 42 ++++++++++++++++++++++++++++++------- artiq/language/__init__.py | 4 +++- artiq/language/types.py | 19 +++++++++++++++++ 3 files changed, 57 insertions(+), 8 deletions(-) create mode 100644 artiq/language/types.py diff --git a/artiq/compiler/embedding.py b/artiq/compiler/embedding.py index 3dacbdc13..c35eec7ed 100644 --- a/artiq/compiler/embedding.py +++ b/artiq/compiler/embedding.py @@ -232,7 +232,7 @@ class Stitcher: quote_function=self._quote_function) return asttyped_rewriter.visit(function_node) - def _function_def_note(self, function): + def _function_loc(self, function): filename = function.__code__.co_filename line = function.__code__.co_firstlineno name = function.__code__.co_name @@ -240,14 +240,36 @@ class Stitcher: source_line = linecache.getline(filename, line) column = re.search("def", source_line).start(0) source_buffer = source.Buffer(source_line, filename, line) - loc = source.Range(source_buffer, column, column) + return source.Range(source_buffer, column, column) + + def _function_def_note(self, function): return diagnostic.Diagnostic("note", "definition of function '{function}'", - {"function": name}, - loc) + {"function": function.__name__}, + self._function_loc(function)) + + def _extract_annot(self, function, annot, kind, call_loc): + if not isinstance(annot, types.Type): + note = diagnostic.Diagnostic("note", + "in function called remotely here", {}, + call_loc) + diag = diagnostic.Diagnostic("error", + "type annotation for {kind}, '{annot}', is not an ARTIQ type", + {"kind": kind, "annot": repr(annot)}, + self._function_loc(function), + notes=[note]) + self.engine.process(diag) + + return types.TVar() + else: + return annot def _type_of_param(self, function, loc, param): - if param.default is not inspect.Parameter.empty: + if param.annotation is not inspect.Parameter.empty: + # Type specified explicitly. + return self._extract_annot(function, param.annotation, + "argument {}".format(param.name), loc) + elif param.default is not inspect.Parameter.empty: # 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. @@ -300,8 +322,14 @@ class Stitcher: else: optarg_types[param.name] = self._type_of_param(function, loc, param) - # Fixed for now. - ret_type = builtins.TInt(types.TValue(32)) + if signature.return_annotation is not inspect.Signature.empty: + ret_type = self._extract_annot(function, signature.return_annotation, + "return type", loc) + else: + diag = diagnostic.Diagnostic("fatal", + "function must have a return type specified to be called remotely", {}, + self._function_loc(function)) + self.engine.process(diag) rpc_type = types.TRPCFunction(arg_types, optarg_types, ret_type, service=self._map(function)) diff --git a/artiq/language/__init__.py b/artiq/language/__init__.py index 6497cda47..e15ae23d4 100644 --- a/artiq/language/__init__.py +++ b/artiq/language/__init__.py @@ -1,5 +1,6 @@ -from artiq.language import core, environment, units, scan +from artiq.language import core, types, environment, units, scan from artiq.language.core import * +from artiq.language.types import * from artiq.language.environment import * from artiq.language.units import * from artiq.language.scan import * @@ -7,6 +8,7 @@ from artiq.language.scan import * __all__ = [] __all__.extend(core.__all__) +__all__.extend(types.__all__) __all__.extend(environment.__all__) __all__.extend(units.__all__) __all__.extend(scan.__all__) diff --git a/artiq/language/types.py b/artiq/language/types.py new file mode 100644 index 000000000..66a8b1b89 --- /dev/null +++ b/artiq/language/types.py @@ -0,0 +1,19 @@ +""" +Values representing ARTIQ types, to be used in function type +annotations. +""" + +from artiq.compiler import types, builtins + +__all__ = ["TNone", "TBool", "TInt32", "TInt64", "TFloat", + "TStr", "TList", "TRange32", "TRange64"] + +TNone = builtins.TNone() +TBool = builtins.TBool() +TInt32 = builtins.TInt(types.TValue(32)) +TInt64 = builtins.TInt(types.TValue(64)) +TFloat = builtins.TFloat() +TStr = builtins.TStr() +TList = builtins.TList +TRange32 = builtins.TRange(builtins.TInt(types.TValue(32))) +TRange64 = builtins.TRange(builtins.TInt(types.TValue(64))) From c72267ecf56ade318854829b1018394cb3a2f482 Mon Sep 17 00:00:00 2001 From: whitequark Date: Mon, 10 Aug 2015 19:25:48 +0300 Subject: [PATCH 214/369] Implement syscalls for the new compiler. --- artiq/compiler/embedding.py | 104 ++++++++++---- .../compiler/transforms/llvm_ir_generator.py | 25 +++- artiq/compiler/types.py | 27 +++- artiq/coredevice/core.py | 13 +- artiq/coredevice/dds.py | 26 +++- artiq/coredevice/runtime.py | 134 ------------------ artiq/coredevice/ttl.py | 43 ++++-- artiq/language/core.py | 76 +++++----- 8 files changed, 223 insertions(+), 225 deletions(-) delete mode 100644 artiq/coredevice/runtime.py diff --git a/artiq/compiler/embedding.py b/artiq/compiler/embedding.py index c35eec7ed..4f79bdfaa 100644 --- a/artiq/compiler/embedding.py +++ b/artiq/compiler/embedding.py @@ -238,6 +238,10 @@ class Stitcher: name = function.__code__.co_name source_line = linecache.getline(filename, line) + while source_line.lstrip().startswith("@"): + line += 1 + source_line = linecache.getline(filename, line) + column = re.search("def", source_line).start(0) source_buffer = source.Buffer(source_line, filename, line) return source.Range(source_buffer, column, column) @@ -248,11 +252,16 @@ class Stitcher: {"function": function.__name__}, self._function_loc(function)) - def _extract_annot(self, function, annot, kind, call_loc): + def _extract_annot(self, function, annot, kind, call_loc, is_syscall): if not isinstance(annot, types.Type): - note = diagnostic.Diagnostic("note", - "in function called remotely here", {}, - call_loc) + if is_syscall: + note = diagnostic.Diagnostic("note", + "in system call here", {}, + call_loc) + else: + note = diagnostic.Diagnostic("note", + "in function called remotely here", {}, + call_loc) diag = diagnostic.Diagnostic("error", "type annotation for {kind}, '{annot}', is not an ARTIQ type", {"kind": kind, "annot": repr(annot)}, @@ -264,11 +273,19 @@ class Stitcher: else: return annot - def _type_of_param(self, function, loc, param): + def _type_of_param(self, function, loc, param, is_syscall): if param.annotation is not inspect.Parameter.empty: # Type specified explicitly. return self._extract_annot(function, param.annotation, - "argument {}".format(param.name), loc) + "argument '{}'".format(param.name), loc, + is_syscall) + elif is_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)) + self.engine.process(diag) elif param.default is not inspect.Parameter.empty: # Try and infer the type from the default value. # This is tricky, because the default value might not have @@ -281,8 +298,8 @@ class Stitcher: def proxy_diagnostic(diag): note = diagnostic.Diagnostic("note", "expanded from here while trying to infer a type for an" - " unannotated optional argument '{param_name}' from its default value", - {"param_name": param.name}, + " unannotated optional argument '{argument}' from its default value", + {"argument": param.name}, loc) diag.notes.append(note) @@ -300,7 +317,7 @@ class Stitcher: # Let the rest of the program decide. return types.TVar() - def _quote_rpc_function(self, function, loc): + def _quote_foreign_function(self, function, loc, syscall): signature = inspect.signature(function) arg_types = OrderedDict() @@ -318,44 +335,73 @@ class Stitcher: continue if param.default is inspect.Parameter.empty: - arg_types[param.name] = self._type_of_param(function, loc, param) + arg_types[param.name] = self._type_of_param(function, loc, param, + is_syscall=syscall is not None) + elif syscall is None: + optarg_types[param.name] = self._type_of_param(function, loc, param, + is_syscall=syscall is not None) else: - optarg_types[param.name] = self._type_of_param(function, loc, param) + diag = diagnostic.Diagnostic("error", + "system call argument '{argument}' must not have a default value", + {"argument": param.name}, + self._function_loc(function)) + 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) - else: - diag = diagnostic.Diagnostic("fatal", - "function must have a return type specified to be called remotely", {}, + "return type", loc, is_syscall=syscall is not None) + elif syscall is None: + diag = diagnostic.Diagnostic("error", + "function must have a return type annotation to be called remotely", {}, self._function_loc(function)) self.engine.process(diag) + ret_type = types.TVar() + else: # syscall is not None + diag = diagnostic.Diagnostic("error", + "system call must have a return type annotation", {}, + self._function_loc(function)) + self.engine.process(diag) + ret_type = types.TVar() - rpc_type = types.TRPCFunction(arg_types, optarg_types, ret_type, - service=self._map(function)) + if syscall is None: + function_type = types.TRPCFunction(arg_types, optarg_types, ret_type, + service=self._map(function)) + function_name = "__rpc_{}__".format(function_type.service) + else: + function_type = types.TCFunction(arg_types, ret_type, + name=syscall) + function_name = "__ffi_{}__".format(function_type.name) - rpc_name = "__rpc_{}__".format(rpc_type.service) - self.globals[rpc_name] = rpc_type - self.functions[function] = rpc_name + self.globals[function_name] = function_type + self.functions[function] = function_name - return rpc_name + return function_name def _quote_function(self, function, loc): if function in self.functions: return self.functions[function] if hasattr(function, "artiq_embedded"): - # Insert the typed AST for the new function and restart inference. - # It doesn't really matter where we insert as long as it is before - # the final call. - function_node = self._quote_embedded_function(function) - self.typedtree.insert(0, function_node) - self.inference_finished = False - return function_node.name + if function.artiq_embedded.function is not None: + # Insert the typed AST for the new function and restart inference. + # It doesn't really matter where we insert as long as it is before + # the final call. + function_node = self._quote_embedded_function(function) + self.typedtree.insert(0, function_node) + self.inference_finished = False + return function_node.name + elif 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. + return self._quote_foreign_function(function, loc, + syscall=function.artiq_embedded.syscall) + else: + assert False else: # Insert a storage-less global whose type instructs the compiler # to perform an RPC instead of a regular call. - return self._quote_rpc_function(function, loc) + return self._quote_foreign_function(function, loc, + syscall=None) def stitch_call(self, function, args, kwargs): function_node = self._quote_embedded_function(function) diff --git a/artiq/compiler/transforms/llvm_ir_generator.py b/artiq/compiler/transforms/llvm_ir_generator.py index 8bda72f5d..4eaf34e68 100644 --- a/artiq/compiler/transforms/llvm_ir_generator.py +++ b/artiq/compiler/transforms/llvm_ir_generator.py @@ -175,7 +175,7 @@ class LLVMIRGenerator: 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_function(typ): + elif types.is_rpc_function(typ) or types.is_c_function(typ): if for_return: return llvoid else: @@ -731,11 +731,20 @@ class LLVMIRGenerator: return llvalue def _prepare_closure_call(self, insn): - llclosure, llargs = self.map(insn.target_function()), map(self.map, insn.arguments()) - llenv = self.llbuilder.extract_value(llclosure, 0) - llfun = self.llbuilder.extract_value(llclosure, 1) + llclosure = self.map(insn.target_function()) + llargs = [self.map(arg) for arg in insn.arguments()] + llenv = self.llbuilder.extract_value(llclosure, 0) + llfun = self.llbuilder.extract_value(llclosure, 1) return llfun, [llenv] + list(llargs) + def _prepare_ffi_call(self, insn): + llargs = [self.map(arg) for arg in insn.arguments()] + llfunty = ll.FunctionType(self.llty_of_type(insn.type, for_return=True), + [llarg.type for llarg in llargs]) + llfun = ll.Function(self.llmodule, llfunty, + insn.target_function().type.name) + return llfun, list(llargs) + # See session.c:{send,receive}_rpc_value and comm_generic.py:_{send,receive}_rpc_value. def _rpc_tag(self, typ, error_handler): if types.is_tuple(typ): @@ -869,6 +878,10 @@ class LLVMIRGenerator: insn.target_function().type, insn.arguments(), llnormalblock=None, llunwindblock=None) + elif types.is_c_function(insn.target_function().type): + llfun, llargs = self._prepare_ffi_call(insn) + return self.llbuilder.call(llfun, llargs, + name=insn.name) else: llfun, llargs = self._prepare_closure_call(insn) return self.llbuilder.call(llfun, llargs, @@ -882,6 +895,10 @@ class LLVMIRGenerator: insn.target_function().type, insn.arguments(), llnormalblock, llunwindblock) + elif types.is_c_function(insn.target_function().type): + llfun, llargs = self._prepare_ffi_call(insn) + return self.llbuilder.invoke(llfun, llargs, llnormalblock, llunwindblock, + name=insn.name) else: llfun, llargs = self._prepare_closure_call(insn) return self.llbuilder.invoke(llfun, llargs, llnormalblock, llunwindblock, diff --git a/artiq/compiler/types.py b/artiq/compiler/types.py index a43a235ef..631a9107e 100644 --- a/artiq/compiler/types.py +++ b/artiq/compiler/types.py @@ -258,6 +258,26 @@ class TRPCFunction(TFunction): else: raise UnificationError(self, other) +class TCFunction(TFunction): + """ + A function type of a runtime-provided C function. + + :ivar name: (str) C function name + """ + + def __init__(self, args, ret, name): + super().__init__(args, OrderedDict(), ret) + self.name = name + + def unify(self, other): + if isinstance(other, TCFunction) and \ + self.name == other.name: + super().unify(other) + elif isinstance(other, TVar): + other.unify(self) + else: + raise UnificationError(self, other) + class TBuiltin(Type): """ An instance of builtin type. Every instance of a builtin @@ -371,6 +391,9 @@ def is_function(typ): def is_rpc_function(typ): return isinstance(typ.find(), TRPCFunction) +def is_c_function(typ): + return isinstance(typ.find(), TCFunction) + def is_builtin(typ, name=None): typ = typ.find() if name is None: @@ -423,7 +446,7 @@ class TypePrinter(object): return "(%s,)" % self.name(typ.elts[0]) else: return "(%s)" % ", ".join(list(map(self.name, typ.elts))) - elif isinstance(typ, (TFunction, TRPCFunction)): + elif isinstance(typ, (TFunction, TRPCFunction, TCFunction)): args = [] args += [ "%s:%s" % (arg, self.name(typ.args[arg])) for arg in typ.args] args += ["?%s:%s" % (arg, self.name(typ.optargs[arg])) for arg in typ.optargs] @@ -431,6 +454,8 @@ class TypePrinter(object): if isinstance(typ, TRPCFunction): return "rpc({}) {}".format(typ.service, signature) + if isinstance(typ, TCFunction): + return "ffi({}) {}".format(repr(typ.name), signature) elif isinstance(typ, TFunction): return signature elif isinstance(typ, TBuiltinFunction): diff --git a/artiq/coredevice/core.py b/artiq/coredevice/core.py index fb54cc9a3..1adb1c904 100644 --- a/artiq/coredevice/core.py +++ b/artiq/coredevice/core.py @@ -1,9 +1,9 @@ -import sys, tempfile +import sys from pythonparser import diagnostic from artiq.language.core import * -from artiq.language.units import ns +from artiq.language.types import * from artiq.compiler import Stitcher, Module from artiq.compiler.targets import OR1KTarget @@ -15,6 +15,11 @@ from artiq.coredevice import exceptions class CompileError(Exception): pass + +@syscall +def rtio_get_counter() -> TInt64: + raise NotImplementedError("syscall not simulated") + class Core: def __init__(self, dmgr, ref_period=8*ns, external_clock=False): self.comm = dmgr.get("comm") @@ -58,8 +63,8 @@ class Core: @kernel def get_rtio_counter_mu(self): - return syscall("rtio_get_counter") + return rtio_get_counter() @kernel def break_realtime(self): - at_mu(syscall("rtio_get_counter") + 125000) + at_mu(rtio_get_counter() + 125000) diff --git a/artiq/coredevice/dds.py b/artiq/coredevice/dds.py index 9ba4584d8..2f03ff661 100644 --- a/artiq/coredevice/dds.py +++ b/artiq/coredevice/dds.py @@ -9,6 +9,24 @@ PHASE_MODE_ABSOLUTE = 1 PHASE_MODE_TRACKING = 2 +@syscall +def dds_init(time_mu: TInt64, channel: TInt32) -> TNone: + raise NotImplementedError("syscall not simulated") + +@syscall +def dds_batch_enter(time_mu: TInt64) -> TNone: + raise NotImplementedError("syscall not simulated") + +@syscall +def dds_batch_exit() -> TNone: + raise NotImplementedError("syscall not simulated") + +@syscall +def dds_set(time_mu: TInt64, channel: TInt32, ftw: TInt32, + pow: TInt32, phase_mode: TInt32) -> TNone: + raise NotImplementedError("syscall not simulated") + + class _BatchContextManager: def __init__(self, dds_bus): self.dds_bus = dds_bus @@ -34,13 +52,13 @@ class DDSBus: def batch_enter(self): """Starts a DDS command batch. All DDS commands are buffered after this call, until ``batch_exit`` is called.""" - syscall("dds_batch_enter", now_mu()) + dds_batch_enter(now_mu()) @kernel def batch_exit(self): """Ends a DDS command batch. All buffered DDS commands are issued on the bus, and FUD is pulsed at the time the batch started.""" - syscall("dds_batch_exit") + dds_batch_exit() class _DDSGeneric: @@ -91,7 +109,7 @@ class _DDSGeneric: """Resets and initializes the DDS channel. The runtime does this for all channels upon core device startup.""" - syscall("dds_init", now_mu(), self.channel) + dds_init(now_mu(), self.channel) @kernel def set_phase_mode(self, phase_mode): @@ -128,7 +146,7 @@ class _DDSGeneric: """ if phase_mode == _PHASE_MODE_DEFAULT: phase_mode = self.phase_mode - syscall("dds_set", now_mu(), self.channel, + dds_set(now_mu(), self.channel, frequency, round(phase*2**self.pow_width), phase_mode) @kernel diff --git a/artiq/coredevice/runtime.py b/artiq/coredevice/runtime.py deleted file mode 100644 index e716aecfc..000000000 --- a/artiq/coredevice/runtime.py +++ /dev/null @@ -1,134 +0,0 @@ -import os - -import llvmlite_or1k.ir as ll -import llvmlite_or1k.binding as llvm - -from artiq.language import units - -_syscalls = { - "now_init": "n:I", - "now_save": "I:n", - "watchdog_set": "i:i", - "watchdog_clear": "i:n", - "rtio_get_counter": "n:I", - "ttl_set_o": "Iib:n", - "ttl_set_oe": "Iib:n", - "ttl_set_sensitivity": "Iii:n", - "ttl_get": "iI:I", - "ttl_clock_set": "Iii:n", - "dds_init": "Ii:n", - "dds_batch_enter": "I:n", - "dds_batch_exit": "n:n", - "dds_set": "Iiiii:n", -} - - -def _chr_to_type(c): - if c == "n": - return ll.VoidType() - if c == "b": - return ll.IntType(1) - if c == "i": - return ll.IntType(32) - if c == "I": - return ll.IntType(64) - raise ValueError - - -def _str_to_functype(s): - assert(s[-2] == ":") - type_ret = _chr_to_type(s[-1]) - type_args = [_chr_to_type(c) for c in s[:-2] if c != "n"] - return ll.FunctionType(type_ret, type_args) - - -def _chr_to_value(c): - if c == "n": - return base_types.VNone() - if c == "b": - return base_types.VBool() - if c == "i": - return base_types.VInt() - if c == "I": - return base_types.VInt(64) - raise ValueError - - -def _value_to_str(v): - if isinstance(v, base_types.VNone): - return "n" - if isinstance(v, base_types.VBool): - return "b" - if isinstance(v, base_types.VInt): - if v.nbits == 32: - return "i" - if v.nbits == 64: - return "I" - raise ValueError - if isinstance(v, base_types.VFloat): - return "f" - if isinstance(v, fractions.VFraction): - return "F" - if isinstance(v, lists.VList): - return "l" + _value_to_str(v.el_type) - raise ValueError - - -class LinkInterface: - def init_module(self, module): - self.module = module - llvm_module = self.module.llvm_module - - # RPC - func_type = ll.FunctionType(ll.IntType(32), [ll.IntType(32)], - var_arg=1) - self.rpc = ll.Function(llvm_module, func_type, "__syscall_rpc") - - # syscalls - self.syscalls = dict() - for func_name, func_type_str in _syscalls.items(): - func_type = _str_to_functype(func_type_str) - self.syscalls[func_name] = ll.Function( - llvm_module, func_type, "__syscall_" + func_name) - - def _build_rpc(self, args, builder): - r = base_types.VInt() - if builder is not None: - new_args = [] - new_args.append(args[0].auto_load(builder)) # RPC number - for arg in args[1:]: - # type tag - arg_type_str = _value_to_str(arg) - arg_type_int = 0 - for c in reversed(arg_type_str): - arg_type_int <<= 8 - arg_type_int |= ord(c) - new_args.append(ll.Constant(ll.IntType(32), arg_type_int)) - - # pointer to value - if not isinstance(arg, base_types.VNone): - if isinstance(arg.llvm_value.type, ll.PointerType): - new_args.append(arg.llvm_value) - else: - arg_ptr = arg.new() - arg_ptr.alloca(builder) - arg_ptr.auto_store(builder, arg.llvm_value) - new_args.append(arg_ptr.llvm_value) - # end marker - new_args.append(ll.Constant(ll.IntType(32), 0)) - r.auto_store(builder, builder.call(self.rpc, new_args)) - return r - - def _build_regular_syscall(self, syscall_name, args, builder): - r = _chr_to_value(_syscalls[syscall_name][-1]) - if builder is not None: - args = [arg.auto_load(builder) for arg in args] - r.auto_store(builder, builder.call(self.syscalls[syscall_name], - args)) - return r - - def build_syscall(self, syscall_name, args, builder): - if syscall_name == "rpc": - return self._build_rpc(args, builder) - else: - return self._build_regular_syscall(syscall_name, args, builder) diff --git a/artiq/coredevice/ttl.py b/artiq/coredevice/ttl.py index 866e884e5..ffc3ca0c4 100644 --- a/artiq/coredevice/ttl.py +++ b/artiq/coredevice/ttl.py @@ -1,4 +1,26 @@ from artiq.language.core import * +from artiq.language.types import * + + +@syscall +def ttl_set_o(time_mu: TInt64, channel: TInt32, enabled: TBool) -> TNone: + raise NotImplementedError("syscall not simulated") + +@syscall +def ttl_set_oe(time_mu: TInt64, channel: TInt32, enabled: TBool) -> TNone: + raise NotImplementedError("syscall not simulated") + +@syscall +def ttl_set_sensitivity(time_mu: TInt64, channel: TInt32, sensitivity: TInt32) -> TNone: + raise NotImplementedError("syscall not simulated") + +@syscall +def ttl_get(channel: TInt32, time_limit_mu: TInt64) -> TInt64: + raise NotImplementedError("syscall not simulated") + +@syscall +def ttl_clock_set(time_mu: TInt64, channel: TInt32, ftw: TInt32) -> TNone: + raise NotImplementedError("syscall not simulated") class TTLOut: @@ -18,14 +40,14 @@ class TTLOut: @kernel def set_o(self, o): - syscall("ttl_set_o", now_mu(), self.channel, o) + ttl_set_o(now_mu(), self.channel, o) self.o_previous_timestamp = now_mu() @kernel def sync(self): """Busy-wait until all programmed level switches have been effected.""" - while syscall("rtio_get_counter") < self.o_previous_timestamp: + while self.core.get_rtio_counter_mu() < self.o_previous_timestamp: pass @kernel @@ -83,7 +105,7 @@ class TTLInOut: @kernel def set_oe(self, oe): - syscall("ttl_set_oe", now_mu(), self.channel, oe) + ttl_set_oe(now_mu(), self.channel, oe) @kernel def output(self): @@ -95,14 +117,14 @@ class TTLInOut: @kernel def set_o(self, o): - syscall("ttl_set_o", now_mu(), self.channel, o) + ttl_set_o(now_mu(), self.channel, o) self.o_previous_timestamp = now_mu() @kernel def sync(self): """Busy-wait until all programmed level switches have been effected.""" - while syscall("rtio_get_counter") < self.o_previous_timestamp: + while self.core.get_rtio_counter_mu() < self.o_previous_timestamp: pass @kernel @@ -133,7 +155,7 @@ class TTLInOut: @kernel def _set_sensitivity(self, value): - syscall("ttl_set_sensitivity", now_mu(), self.channel, value) + ttl_set_sensitivity(now_mu(), self.channel, value) self.i_previous_timestamp = now_mu() @kernel @@ -189,8 +211,7 @@ class TTLInOut: """Poll the RTIO input during all the previously programmed gate openings, and returns the number of registered events.""" count = 0 - while syscall("ttl_get", self.channel, - self.i_previous_timestamp) >= 0: + while ttl_get(self.channel, self.i_previous_timestamp) >= 0: count += 1 return count @@ -201,7 +222,7 @@ class TTLInOut: If the gate is permanently closed, returns a negative value. """ - return syscall("ttl_get", self.channel, self.i_previous_timestamp) + return ttl_get(self.channel, self.i_previous_timestamp) class TTLClockGen: @@ -254,7 +275,7 @@ class TTLClockGen: that are not powers of two cause jitter of one RTIO clock cycle at the output. """ - syscall("ttl_clock_set", now_mu(), self.channel, frequency) + ttl_clock_set(now_mu(), self.channel, frequency) self.previous_timestamp = now_mu() @kernel @@ -271,5 +292,5 @@ class TTLClockGen: def sync(self): """Busy-wait until all programmed frequency switches and stops have been effected.""" - while syscall("rtio_get_counter") < self.o_previous_timestamp: + while self.core.get_rtio_counter_mu() < self.o_previous_timestamp: pass diff --git a/artiq/language/core.py b/artiq/language/core.py index b4f547d35..b0d556969 100644 --- a/artiq/language/core.py +++ b/artiq/language/core.py @@ -7,15 +7,19 @@ from collections import namedtuple from functools import wraps -__all__ = ["int64", "round64", "kernel", "portable", - "set_time_manager", "set_syscall_manager", "set_watchdog_factory", + +__all__ = ["int64", "round64", + "kernel", "portable", "syscall", + "set_time_manager", "set_watchdog_factory", "ARTIQException"] # global namespace for kernels -kernel_globals = ("sequential", "parallel", +kernel_globals = ( + "sequential", "parallel", "delay_mu", "now_mu", "at_mu", "delay", "seconds_to_mu", "mu_to_seconds", - "syscall", "watchdog") + "watchdog" +) __all__.extend(kernel_globals) @@ -78,11 +82,12 @@ def round64(x): return int64(round(x)) -_ARTIQEmbeddedInfo = namedtuple("_ARTIQEmbeddedInfo", "core_name function") - +_ARTIQEmbeddedInfo = namedtuple("_ARTIQEmbeddedInfo", + "core_name function syscall") def kernel(arg): - """This decorator marks an object's method for execution on the core + """ + This decorator marks an object's method for execution on the core device. When a decorated method is called from the Python interpreter, the ``core`` @@ -106,15 +111,15 @@ def kernel(arg): def run_on_core(self, *k_args, **k_kwargs): return getattr(self, arg).run(function, ((self,) + k_args), k_kwargs) run_on_core.artiq_embedded = _ARTIQEmbeddedInfo( - core_name=arg, function=function) + core_name=arg, function=function, syscall=None) return run_on_core return inner_decorator else: return kernel("core")(arg) - def portable(function): - """This decorator marks a function for execution on the same device as its + """ + This decorator marks a function for execution on the same device as its caller. In other words, a decorated function called from the interpreter on the @@ -122,9 +127,31 @@ def portable(function): core device). A decorated function called from a kernel will be executed on the core device (no RPC). """ - function.artiq_embedded = _ARTIQEmbeddedInfo(core_name="", function=function) + function.artiq_embedded = \ + _ARTIQEmbeddedInfo(core_name=None, function=function, syscall=None) return function +def syscall(arg): + """ + This decorator marks a function as a system call. When executed on a core + device, a C function with the provided name (or the same name as + the Python function, if not provided) will be called. When executed on + host, the Python function will be called as usual. + + Every argument and the return value must be annotated with ARTIQ types. + + Only drivers should normally define syscalls. + """ + if isinstance(arg, str): + def inner_decorator(function): + function.artiq_embedded = \ + _ARTIQEmbeddedInfo(core_name=None, function=None, + syscall=function.__name__) + return function + return inner_decorator + else: + return syscall(arg.__name__)(arg) + class _DummyTimeManager: def _not_implemented(self, *args, **kwargs): @@ -152,22 +179,6 @@ def set_time_manager(time_manager): _time_manager = time_manager -class _DummySyscallManager: - def do(self, *args): - raise NotImplementedError( - "Attempted to interpret kernel without a syscall manager") - -_syscall_manager = _DummySyscallManager() - - -def set_syscall_manager(syscall_manager): - """Set the system call manager used for simulating the core device's - runtime in the Python interpreter. - """ - global _syscall_manager - _syscall_manager = syscall_manager - - class _Sequential: """In a sequential block, statements are executed one after another, with the time increasing as one moves down the statement list.""" @@ -240,17 +251,6 @@ def mu_to_seconds(mu, core=None): return mu*core.ref_period -def syscall(*args): - """Invokes a service of the runtime. - - Kernels use this function to interface to the outside world: program RTIO - events, make RPCs, etc. - - Only drivers should normally use ``syscall``. - """ - return _syscall_manager.do(*args) - - class _DummyWatchdog: def __init__(self, timeout): pass From 46476516ba6d35640d293148b1a229eb74a9e7a5 Mon Sep 17 00:00:00 2001 From: whitequark Date: Mon, 10 Aug 2015 19:26:00 +0300 Subject: [PATCH 215/369] ARTIQException: tell linecache where to look for runtime sources. Runtime sources can appear in the backtrace when artiq_raise_from_c is used. --- artiq/coredevice/runtime.py | 13 +++++++++++++ artiq/language/core.py | 5 ++++- soc/runtime/artiq_personality.h | 3 ++- 3 files changed, 19 insertions(+), 2 deletions(-) create mode 100644 artiq/coredevice/runtime.py diff --git a/artiq/coredevice/runtime.py b/artiq/coredevice/runtime.py new file mode 100644 index 000000000..a9d74b63d --- /dev/null +++ b/artiq/coredevice/runtime.py @@ -0,0 +1,13 @@ +import os + +class SourceLoader: + def __init__(self, runtime_root): + self.runtime_root = runtime_root + + def get_source(self, filename): + print(os.path.join(self.runtime_root, filename)) + with open(os.path.join(self.runtime_root, filename)) as f: + return f.read() + +artiq_root = os.path.join(os.path.dirname(__file__), "..", "..") +source_loader = SourceLoader(os.path.join(artiq_root, "soc", "runtime")) diff --git a/artiq/language/core.py b/artiq/language/core.py index b0d556969..8ad224f51 100644 --- a/artiq/language/core.py +++ b/artiq/language/core.py @@ -6,6 +6,8 @@ import linecache, re from collections import namedtuple from functools import wraps +# for runtime files in backtraces +from artiq.coredevice.runtime import source_loader __all__ = ["int64", "round64", @@ -312,7 +314,8 @@ class ARTIQException(Exception): lines.append("Core Device Traceback (most recent call last):") for (filename, line, column, function, address) in self.traceback: - source_line = linecache.getline(filename, line) + stub_globals = {"__name__": filename, "__loader__": source_loader} + source_line = linecache.getline(filename, line, stub_globals) indentation = re.search(r"^\s*", source_line).end() if address is None: diff --git a/soc/runtime/artiq_personality.h b/soc/runtime/artiq_personality.h index 11a10b2b9..9e7ddce3e 100644 --- a/soc/runtime/artiq_personality.h +++ b/soc/runtime/artiq_personality.h @@ -40,7 +40,8 @@ void __artiq_reraise(void) .param = { exnparam0, exnparam1, exnparam2 }, \ .file = __FILE__, \ .line = __LINE__, \ - .column = 0 \ + .column = -1, \ + .function = __func__, \ }; \ __artiq_raise(&exn); \ } while(0) From 62e6f8a03decf822e42260593ce6467fb6d15c1c Mon Sep 17 00:00:00 2001 From: whitequark Date: Mon, 10 Aug 2015 20:25:57 +0300 Subject: [PATCH 216/369] compiler.embedding.Stitcher: refactor. --- artiq/compiler/embedding.py | 17 +++++++++++++++-- artiq/coredevice/core.py | 1 + 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/artiq/compiler/embedding.py b/artiq/compiler/embedding.py index 4f79bdfaa..ec1b629e3 100644 --- a/artiq/compiler/embedding.py +++ b/artiq/compiler/embedding.py @@ -174,7 +174,7 @@ class Stitcher: self.inverse_rpc_map[obj_id] = self.next_rpc return self.next_rpc - def _iterate(self): + def finalize(self): inferencer = Inferencer(engine=self.engine) # Iterate inference to fixed point. @@ -414,4 +414,17 @@ class Stitcher: synthesizer.finalize() self.typedtree.append(call_node) - self._iterate() + def finalize(self): + inferencer = Inferencer(engine=self.engine) + + # Iterate inference to fixed point. + self.inference_finished = False + while not self.inference_finished: + self.inference_finished = True + inferencer.visit(self.typedtree) + + # 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)) diff --git a/artiq/coredevice/core.py b/artiq/coredevice/core.py index 1adb1c904..9408693b7 100644 --- a/artiq/coredevice/core.py +++ b/artiq/coredevice/core.py @@ -36,6 +36,7 @@ class Core: stitcher = Stitcher(engine=engine) stitcher.stitch_call(function, args, kwargs) + stitcher.finalize() module = Module(stitcher) target = OR1KTarget() From 200330a808186f731ab4cc0166b5bf2c4248ef9e Mon Sep 17 00:00:00 2001 From: whitequark Date: Mon, 10 Aug 2015 20:36:39 +0300 Subject: [PATCH 217/369] Remove parts of py2llvm that are implemented in the new compiler. --- artiq/py2llvm_old/__init__.py | 6 - artiq/py2llvm_old/ast_body.py | 539 ---------------- artiq/py2llvm_old/base_types.py | 321 ---------- artiq/py2llvm_old/infer_types.py | 75 --- artiq/py2llvm_old/iterators.py | 51 -- artiq/py2llvm_old/lists.py | 72 --- artiq/py2llvm_old/module.py | 62 -- artiq/py2llvm_old/test/py2llvm.py | 169 +++++ artiq/py2llvm_old/tools.py | 5 - artiq/{ => py2llvm_old}/transforms/inline.py | 0 .../transforms/interleave.py | 0 .../transforms/lower_time.py | 0 .../transforms/quantize_time.py | 0 .../transforms/unroll_loops.py | 0 artiq/py2llvm_old/values.py | 94 --- artiq/test/py2llvm.py | 372 ----------- artiq/test/transforms.py | 44 -- artiq/transforms/__init__.py | 0 artiq/transforms/fold_constants.py | 156 ----- artiq/transforms/remove_dead_code.py | 59 -- artiq/transforms/remove_inter_assigns.py | 149 ----- artiq/transforms/tools.py | 141 ---- artiq/transforms/unparse.py | 600 ------------------ 23 files changed, 169 insertions(+), 2746 deletions(-) delete mode 100644 artiq/py2llvm_old/__init__.py delete mode 100644 artiq/py2llvm_old/ast_body.py delete mode 100644 artiq/py2llvm_old/base_types.py delete mode 100644 artiq/py2llvm_old/infer_types.py delete mode 100644 artiq/py2llvm_old/iterators.py delete mode 100644 artiq/py2llvm_old/lists.py delete mode 100644 artiq/py2llvm_old/module.py create mode 100644 artiq/py2llvm_old/test/py2llvm.py delete mode 100644 artiq/py2llvm_old/tools.py rename artiq/{ => py2llvm_old}/transforms/inline.py (100%) rename artiq/{ => py2llvm_old}/transforms/interleave.py (100%) rename artiq/{ => py2llvm_old}/transforms/lower_time.py (100%) rename artiq/{ => py2llvm_old}/transforms/quantize_time.py (100%) rename artiq/{ => py2llvm_old}/transforms/unroll_loops.py (100%) delete mode 100644 artiq/py2llvm_old/values.py delete mode 100644 artiq/test/py2llvm.py delete mode 100644 artiq/test/transforms.py delete mode 100644 artiq/transforms/__init__.py delete mode 100644 artiq/transforms/fold_constants.py delete mode 100644 artiq/transforms/remove_dead_code.py delete mode 100644 artiq/transforms/remove_inter_assigns.py delete mode 100644 artiq/transforms/tools.py delete mode 100644 artiq/transforms/unparse.py diff --git a/artiq/py2llvm_old/__init__.py b/artiq/py2llvm_old/__init__.py deleted file mode 100644 index ebb8a93af..000000000 --- a/artiq/py2llvm_old/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -from artiq.py2llvm.module import Module - -def get_runtime_binary(runtime, func_def): - module = Module(runtime) - module.compile_function(func_def, dict()) - return module.emit_object() diff --git a/artiq/py2llvm_old/ast_body.py b/artiq/py2llvm_old/ast_body.py deleted file mode 100644 index b310ae78c..000000000 --- a/artiq/py2llvm_old/ast_body.py +++ /dev/null @@ -1,539 +0,0 @@ -from pythonparser import ast - -import llvmlite_or1k.ir as ll - -from artiq.py2llvm import values, base_types, fractions, lists, iterators -from artiq.py2llvm.tools import is_terminated - - -_ast_unops = { - ast.Invert: "o_inv", - ast.Not: "o_not", - ast.UAdd: "o_pos", - ast.USub: "o_neg" -} - -_ast_binops = { - ast.Add: values.operators.add, - ast.Sub: values.operators.sub, - ast.Mult: values.operators.mul, - ast.Div: values.operators.truediv, - ast.FloorDiv: values.operators.floordiv, - ast.Mod: values.operators.mod, - ast.Pow: values.operators.pow, - ast.LShift: values.operators.lshift, - ast.RShift: values.operators.rshift, - ast.BitOr: values.operators.or_, - ast.BitXor: values.operators.xor, - ast.BitAnd: values.operators.and_ -} - -_ast_cmps = { - ast.Eq: values.operators.eq, - ast.NotEq: values.operators.ne, - ast.Lt: values.operators.lt, - ast.LtE: values.operators.le, - ast.Gt: values.operators.gt, - ast.GtE: values.operators.ge -} - - -class Visitor: - def __init__(self, runtime, ns, builder=None): - self.runtime = runtime - self.ns = ns - self.builder = builder - self._break_stack = [] - self._continue_stack = [] - self._active_exception_stack = [] - self._exception_level_stack = [0] - - # builder can be None for visit_expression - def visit_expression(self, node): - method = "_visit_expr_" + node.__class__.__name__ - try: - visitor = getattr(self, method) - except AttributeError: - raise NotImplementedError("Unsupported node '{}' in expression" - .format(node.__class__.__name__)) - return visitor(node) - - def _visit_expr_Name(self, node): - try: - r = self.ns[node.id] - except KeyError: - raise NameError("Name '{}' is not defined".format(node.id)) - return r - - def _visit_expr_NameConstant(self, node): - v = node.value - if v is None: - r = base_types.VNone() - elif isinstance(v, bool): - r = base_types.VBool() - else: - raise NotImplementedError - if self.builder is not None: - r.set_const_value(self.builder, v) - return r - - def _visit_expr_Num(self, node): - n = node.n - if isinstance(n, int): - if abs(n) < 2**31: - r = base_types.VInt() - else: - r = base_types.VInt(64) - elif isinstance(n, float): - r = base_types.VFloat() - else: - raise NotImplementedError - if self.builder is not None: - r.set_const_value(self.builder, n) - return r - - def _visit_expr_UnaryOp(self, node): - value = self.visit_expression(node.operand) - return getattr(value, _ast_unops[type(node.op)])(self.builder) - - def _visit_expr_BinOp(self, node): - return _ast_binops[type(node.op)](self.visit_expression(node.left), - self.visit_expression(node.right), - self.builder) - - def _visit_expr_BoolOp(self, node): - if self.builder is not None: - initial_block = self.builder.basic_block - function = initial_block.function - merge_block = function.append_basic_block("b_merge") - - test_blocks = [] - test_values = [] - for i, value in enumerate(node.values): - if self.builder is not None: - test_block = function.append_basic_block("b_{}_test".format(i)) - test_blocks.append(test_block) - self.builder.position_at_end(test_block) - test_values.append(self.visit_expression(value)) - - result = test_values[0].new() - for value in test_values[1:]: - result.merge(value) - - if self.builder is not None: - self.builder.position_at_end(initial_block) - result.alloca(self.builder, "b_result") - self.builder.branch(test_blocks[0]) - - next_test_blocks = test_blocks[1:] - next_test_blocks.append(None) - for block, next_block, value in zip(test_blocks, - next_test_blocks, - test_values): - self.builder.position_at_end(block) - bval = value.o_bool(self.builder) - result.auto_store(self.builder, - value.auto_load(self.builder)) - if next_block is None: - self.builder.branch(merge_block) - else: - if isinstance(node.op, ast.Or): - self.builder.cbranch(bval.auto_load(self.builder), - merge_block, - next_block) - elif isinstance(node.op, ast.And): - self.builder.cbranch(bval.auto_load(self.builder), - next_block, - merge_block) - else: - raise NotImplementedError - self.builder.position_at_end(merge_block) - - return result - - def _visit_expr_Compare(self, node): - comparisons = [] - old_comparator = self.visit_expression(node.left) - for op, comparator_a in zip(node.ops, node.comparators): - comparator = self.visit_expression(comparator_a) - comparison = _ast_cmps[type(op)](old_comparator, comparator, - self.builder) - comparisons.append(comparison) - old_comparator = comparator - r = comparisons[0] - for comparison in comparisons[1:]: - r = values.operators.and_(r, comparison) - return r - - def _visit_expr_Call(self, node): - fn = node.func.id - if fn in {"bool", "int", "int64", "round", "round64", "float", "len"}: - value = self.visit_expression(node.args[0]) - return getattr(value, "o_" + fn)(self.builder) - elif fn == "Fraction": - r = fractions.VFraction() - if self.builder is not None: - numerator = self.visit_expression(node.args[0]) - denominator = self.visit_expression(node.args[1]) - r.set_value_nd(self.builder, numerator, denominator) - return r - elif fn == "range": - return iterators.IRange( - self.builder, - [self.visit_expression(arg) for arg in node.args]) - elif fn == "syscall": - return self.runtime.build_syscall( - node.args[0].s, - [self.visit_expression(expr) for expr in node.args[1:]], - self.builder) - else: - raise NameError("Function '{}' is not defined".format(fn)) - - def _visit_expr_Attribute(self, node): - value = self.visit_expression(node.value) - return value.o_getattr(node.attr, self.builder) - - def _visit_expr_List(self, node): - elts = [self.visit_expression(elt) for elt in node.elts] - if elts: - el_type = elts[0].new() - for elt in elts[1:]: - el_type.merge(elt) - else: - el_type = base_types.VNone() - count = len(elts) - r = lists.VList(el_type, count) - r.elts = elts - return r - - def _visit_expr_ListComp(self, node): - if len(node.generators) != 1: - raise NotImplementedError - generator = node.generators[0] - if not isinstance(generator, ast.comprehension): - raise NotImplementedError - if not isinstance(generator.target, ast.Name): - raise NotImplementedError - target = generator.target.id - if not isinstance(generator.iter, ast.Call): - raise NotImplementedError - if not isinstance(generator.iter.func, ast.Name): - raise NotImplementedError - if generator.iter.func.id != "range": - raise NotImplementedError - if len(generator.iter.args) != 1: - raise NotImplementedError - if not isinstance(generator.iter.args[0], ast.Num): - raise NotImplementedError - count = generator.iter.args[0].n - - # Prevent incorrect use of the generator target, if it is defined in - # the local function namespace. - if target in self.ns: - old_target_val = self.ns[target] - del self.ns[target] - else: - old_target_val = None - elt = self.visit_expression(node.elt) - if old_target_val is not None: - self.ns[target] = old_target_val - - el_type = elt.new() - r = lists.VList(el_type, count) - r.elt = elt - return r - - def _visit_expr_Subscript(self, node): - value = self.visit_expression(node.value) - if isinstance(node.slice, ast.Index): - index = self.visit_expression(node.slice.value) - else: - raise NotImplementedError - return value.o_subscript(index, self.builder) - - def visit_statements(self, stmts): - for node in stmts: - node_type = node.__class__.__name__ - method = "_visit_stmt_" + node_type - try: - visitor = getattr(self, method) - except AttributeError: - raise NotImplementedError("Unsupported node '{}' in statement" - .format(node_type)) - visitor(node) - if node_type in ("Return", "Break", "Continue"): - break - - def _bb_terminated(self): - return is_terminated(self.builder.basic_block) - - def _visit_stmt_Assign(self, node): - val = self.visit_expression(node.value) - if isinstance(node.value, ast.List): - if len(node.targets) > 1: - raise NotImplementedError - target = self.visit_expression(node.targets[0]) - target.set_count(self.builder, val.alloc_count) - for i, elt in enumerate(val.elts): - idx = base_types.VInt() - idx.set_const_value(self.builder, i) - target.o_subscript(idx, self.builder).set_value(self.builder, - elt) - elif isinstance(node.value, ast.ListComp): - if len(node.targets) > 1: - raise NotImplementedError - target = self.visit_expression(node.targets[0]) - target.set_count(self.builder, val.alloc_count) - - i = base_types.VInt() - i.alloca(self.builder) - i.auto_store(self.builder, ll.Constant(ll.IntType(32), 0)) - - function = self.builder.basic_block.function - copy_block = function.append_basic_block("ai_copy") - end_block = function.append_basic_block("ai_end") - self.builder.branch(copy_block) - - self.builder.position_at_end(copy_block) - target.o_subscript(i, self.builder).set_value(self.builder, - val.elt) - i.auto_store(self.builder, self.builder.add( - i.auto_load(self.builder), - ll.Constant(ll.IntType(32), 1))) - cont = self.builder.icmp_signed( - "<", i.auto_load(self.builder), - ll.Constant(ll.IntType(32), val.alloc_count)) - self.builder.cbranch(cont, copy_block, end_block) - - self.builder.position_at_end(end_block) - else: - for target in node.targets: - target = self.visit_expression(target) - target.set_value(self.builder, val) - - def _visit_stmt_AugAssign(self, node): - target = self.visit_expression(node.target) - right = self.visit_expression(node.value) - val = _ast_binops[type(node.op)](target, right, self.builder) - target.set_value(self.builder, val) - - def _visit_stmt_Expr(self, node): - self.visit_expression(node.value) - - def _visit_stmt_If(self, node): - function = self.builder.basic_block.function - then_block = function.append_basic_block("i_then") - else_block = function.append_basic_block("i_else") - merge_block = function.append_basic_block("i_merge") - - condition = self.visit_expression(node.test).o_bool(self.builder) - self.builder.cbranch(condition.auto_load(self.builder), - then_block, else_block) - - self.builder.position_at_end(then_block) - self.visit_statements(node.body) - if not self._bb_terminated(): - self.builder.branch(merge_block) - - self.builder.position_at_end(else_block) - self.visit_statements(node.orelse) - if not self._bb_terminated(): - self.builder.branch(merge_block) - - self.builder.position_at_end(merge_block) - - def _enter_loop_body(self, break_block, continue_block): - self._break_stack.append(break_block) - self._continue_stack.append(continue_block) - self._exception_level_stack.append(0) - - def _leave_loop_body(self): - self._exception_level_stack.pop() - self._continue_stack.pop() - self._break_stack.pop() - - def _visit_stmt_While(self, node): - function = self.builder.basic_block.function - - body_block = function.append_basic_block("w_body") - else_block = function.append_basic_block("w_else") - condition = self.visit_expression(node.test).o_bool(self.builder) - self.builder.cbranch( - condition.auto_load(self.builder), body_block, else_block) - - continue_block = function.append_basic_block("w_continue") - merge_block = function.append_basic_block("w_merge") - self.builder.position_at_end(body_block) - self._enter_loop_body(merge_block, continue_block) - self.visit_statements(node.body) - self._leave_loop_body() - if not self._bb_terminated(): - self.builder.branch(continue_block) - - self.builder.position_at_end(continue_block) - condition = self.visit_expression(node.test).o_bool(self.builder) - self.builder.cbranch( - condition.auto_load(self.builder), body_block, merge_block) - - self.builder.position_at_end(else_block) - self.visit_statements(node.orelse) - if not self._bb_terminated(): - self.builder.branch(merge_block) - - self.builder.position_at_end(merge_block) - - def _visit_stmt_For(self, node): - function = self.builder.basic_block.function - - it = self.visit_expression(node.iter) - target = self.visit_expression(node.target) - itval = it.get_value_ptr() - - body_block = function.append_basic_block("f_body") - else_block = function.append_basic_block("f_else") - cont = it.o_next(self.builder) - self.builder.cbranch( - cont.auto_load(self.builder), body_block, else_block) - - continue_block = function.append_basic_block("f_continue") - merge_block = function.append_basic_block("f_merge") - self.builder.position_at_end(body_block) - target.set_value(self.builder, itval) - self._enter_loop_body(merge_block, continue_block) - self.visit_statements(node.body) - self._leave_loop_body() - if not self._bb_terminated(): - self.builder.branch(continue_block) - - self.builder.position_at_end(continue_block) - cont = it.o_next(self.builder) - self.builder.cbranch( - cont.auto_load(self.builder), body_block, merge_block) - - self.builder.position_at_end(else_block) - self.visit_statements(node.orelse) - if not self._bb_terminated(): - self.builder.branch(merge_block) - - self.builder.position_at_end(merge_block) - - def _break_loop_body(self, target_block): - exception_levels = self._exception_level_stack[-1] - if exception_levels: - self.runtime.build_pop(self.builder, exception_levels) - self.builder.branch(target_block) - - def _visit_stmt_Break(self, node): - self._break_loop_body(self._break_stack[-1]) - - def _visit_stmt_Continue(self, node): - self._break_loop_body(self._continue_stack[-1]) - - def _visit_stmt_Return(self, node): - if node.value is None: - val = base_types.VNone() - else: - val = self.visit_expression(node.value) - exception_levels = sum(self._exception_level_stack) - if exception_levels: - self.runtime.build_pop(self.builder, exception_levels) - if isinstance(val, base_types.VNone): - self.builder.ret_void() - else: - self.builder.ret(val.auto_load(self.builder)) - - def _visit_stmt_Pass(self, node): - pass - - def _visit_stmt_Raise(self, node): - if self._active_exception_stack: - finally_block, propagate, propagate_eid = ( - self._active_exception_stack[-1]) - self.builder.store(ll.Constant(ll.IntType(1), 1), propagate) - if node.exc is not None: - eid = ll.Constant(ll.IntType(32), node.exc.args[0].n) - self.builder.store(eid, propagate_eid) - self.builder.branch(finally_block) - else: - eid = ll.Constant(ll.IntType(32), node.exc.args[0].n) - self.runtime.build_raise(self.builder, eid) - - def _handle_exception(self, function, finally_block, - propagate, propagate_eid, handlers): - eid = self.runtime.build_getid(self.builder) - self._active_exception_stack.append( - (finally_block, propagate, propagate_eid)) - self.builder.store(ll.Constant(ll.IntType(1), 1), propagate) - self.builder.store(eid, propagate_eid) - - for handler in handlers: - handled_exc_block = function.append_basic_block("try_exc_h") - cont_exc_block = function.append_basic_block("try_exc_c") - if handler.type is None: - self.builder.branch(handled_exc_block) - else: - if isinstance(handler.type, ast.Tuple): - match = self.builder.icmp_signed( - "==", eid, - ll.Constant(ll.IntType(32), - handler.type.elts[0].args[0].n)) - for elt in handler.type.elts[1:]: - match = self.builder.or_( - match, - self.builder.icmp_signed( - "==", eid, - ll.Constant(ll.IntType(32), elt.args[0].n))) - else: - match = self.builder.icmp_signed( - "==", eid, - ll.Constant(ll.IntType(32), handler.type.args[0].n)) - self.builder.cbranch(match, handled_exc_block, cont_exc_block) - self.builder.position_at_end(handled_exc_block) - self.builder.store(ll.Constant(ll.IntType(1), 0), propagate) - self.visit_statements(handler.body) - if not self._bb_terminated(): - self.builder.branch(finally_block) - self.builder.position_at_end(cont_exc_block) - self.builder.branch(finally_block) - - self._active_exception_stack.pop() - - def _visit_stmt_Try(self, node): - function = self.builder.basic_block.function - noexc_block = function.append_basic_block("try_noexc") - exc_block = function.append_basic_block("try_exc") - finally_block = function.append_basic_block("try_finally") - - propagate = self.builder.alloca(ll.IntType(1), - name="propagate") - self.builder.store(ll.Constant(ll.IntType(1), 0), propagate) - propagate_eid = self.builder.alloca(ll.IntType(32), - name="propagate_eid") - exception_occured = self.runtime.build_catch(self.builder) - self.builder.cbranch(exception_occured, exc_block, noexc_block) - - self.builder.position_at_end(noexc_block) - self._exception_level_stack[-1] += 1 - self.visit_statements(node.body) - self._exception_level_stack[-1] -= 1 - if not self._bb_terminated(): - self.runtime.build_pop(self.builder, 1) - self.visit_statements(node.orelse) - if not self._bb_terminated(): - self.builder.branch(finally_block) - self.builder.position_at_end(exc_block) - self._handle_exception(function, finally_block, - propagate, propagate_eid, node.handlers) - - propagate_block = function.append_basic_block("try_propagate") - merge_block = function.append_basic_block("try_merge") - self.builder.position_at_end(finally_block) - self.visit_statements(node.finalbody) - if not self._bb_terminated(): - self.builder.cbranch( - self.builder.load(propagate), - propagate_block, merge_block) - self.builder.position_at_end(propagate_block) - self.runtime.build_raise(self.builder, self.builder.load(propagate_eid)) - self.builder.branch(merge_block) - self.builder.position_at_end(merge_block) diff --git a/artiq/py2llvm_old/base_types.py b/artiq/py2llvm_old/base_types.py deleted file mode 100644 index 3ef472984..000000000 --- a/artiq/py2llvm_old/base_types.py +++ /dev/null @@ -1,321 +0,0 @@ -import llvmlite_or1k.ir as ll - -from artiq.py2llvm.values import VGeneric - - -class VNone(VGeneric): - def get_llvm_type(self): - return ll.VoidType() - - def alloca(self, builder, name): - pass - - def set_const_value(self, builder, v): - assert v is None - - def set_value(self, builder, other): - if not isinstance(other, VNone): - raise TypeError - - def o_bool(self, builder): - r = VBool() - if builder is not None: - r.set_const_value(builder, False) - return r - - def o_not(self, builder): - r = VBool() - if builder is not None: - r.set_const_value(builder, True) - return r - - -class VInt(VGeneric): - def __init__(self, nbits=32): - VGeneric.__init__(self) - self.nbits = nbits - - def get_llvm_type(self): - return ll.IntType(self.nbits) - - def __repr__(self): - return "".format(self.nbits) - - def same_type(self, other): - return isinstance(other, VInt) and other.nbits == self.nbits - - def merge(self, other): - if isinstance(other, VInt) and not isinstance(other, VBool): - if other.nbits > self.nbits: - self.nbits = other.nbits - else: - raise TypeError("Incompatible types: {} and {}" - .format(repr(self), repr(other))) - - def set_value(self, builder, n): - self.auto_store( - builder, n.o_intx(self.nbits, builder).auto_load(builder)) - - def set_const_value(self, builder, n): - self.auto_store(builder, ll.Constant(self.get_llvm_type(), n)) - - def o_bool(self, builder, inv=False): - r = VBool() - if builder is not None: - r.auto_store( - builder, builder.icmp_signed( - "==" if inv else "!=", - self.auto_load(builder), - ll.Constant(self.get_llvm_type(), 0))) - return r - - def o_float(self, builder): - r = VFloat() - if builder is not None: - if isinstance(self, VBool): - cf = builder.uitofp - else: - cf = builder.sitofp - r.auto_store(builder, cf(self.auto_load(builder), - r.get_llvm_type())) - return r - - def o_not(self, builder): - return self.o_bool(builder, inv=True) - - def o_neg(self, builder): - r = VInt(self.nbits) - if builder is not None: - r.auto_store( - builder, builder.mul( - self.auto_load(builder), - ll.Constant(self.get_llvm_type(), -1))) - return r - - def o_intx(self, target_bits, builder): - r = VInt(target_bits) - if builder is not None: - if self.nbits == target_bits: - r.auto_store( - builder, self.auto_load(builder)) - if self.nbits > target_bits: - r.auto_store( - builder, builder.trunc(self.auto_load(builder), - r.get_llvm_type())) - if self.nbits < target_bits: - if isinstance(self, VBool): - ef = builder.zext - else: - ef = builder.sext - r.auto_store( - builder, ef(self.auto_load(builder), - r.get_llvm_type())) - return r - o_roundx = o_intx - - def o_truediv(self, other, builder): - if isinstance(other, VInt): - left = self.o_float(builder) - right = other.o_float(builder) - return left.o_truediv(right, builder) - else: - return NotImplemented - -def _make_vint_binop_method(builder_name, bool_op): - def binop_method(self, other, builder): - if isinstance(other, VInt): - target_bits = max(self.nbits, other.nbits) - if not bool_op and target_bits == 1: - target_bits = 32 - if bool_op and target_bits == 1: - r = VBool() - else: - r = VInt(target_bits) - if builder is not None: - left = self.o_intx(target_bits, builder) - right = other.o_intx(target_bits, builder) - bf = getattr(builder, builder_name) - r.auto_store( - builder, bf(left.auto_load(builder), - right.auto_load(builder))) - return r - else: - return NotImplemented - return binop_method - -for _method_name, _builder_name, _bool_op in (("o_add", "add", False), - ("o_sub", "sub", False), - ("o_mul", "mul", False), - ("o_floordiv", "sdiv", False), - ("o_mod", "srem", False), - ("o_and", "and_", True), - ("o_xor", "xor", True), - ("o_or", "or_", True)): - setattr(VInt, _method_name, _make_vint_binop_method(_builder_name, _bool_op)) - - -def _make_vint_cmp_method(icmp_val): - def cmp_method(self, other, builder): - if isinstance(other, VInt): - r = VBool() - if builder is not None: - target_bits = max(self.nbits, other.nbits) - left = self.o_intx(target_bits, builder) - right = other.o_intx(target_bits, builder) - r.auto_store( - builder, - builder.icmp_signed( - icmp_val, left.auto_load(builder), - right.auto_load(builder))) - return r - else: - return NotImplemented - return cmp_method - -for _method_name, _icmp_val in (("o_eq", "=="), - ("o_ne", "!="), - ("o_lt", "<"), - ("o_le", "<="), - ("o_gt", ">"), - ("o_ge", ">=")): - setattr(VInt, _method_name, _make_vint_cmp_method(_icmp_val)) - - -class VBool(VInt): - def __init__(self): - VInt.__init__(self, 1) - - __repr__ = VGeneric.__repr__ - same_type = VGeneric.same_type - merge = VGeneric.merge - - def set_const_value(self, builder, b): - VInt.set_const_value(self, builder, int(b)) - - -class VFloat(VGeneric): - def get_llvm_type(self): - return ll.DoubleType() - - def set_value(self, builder, v): - if not isinstance(v, VFloat): - raise TypeError - self.auto_store(builder, v.auto_load(builder)) - - def set_const_value(self, builder, n): - self.auto_store(builder, ll.Constant(self.get_llvm_type(), n)) - - def o_float(self, builder): - r = VFloat() - if builder is not None: - r.auto_store(builder, self.auto_load(builder)) - return r - - def o_bool(self, builder, inv=False): - r = VBool() - if builder is not None: - r.auto_store( - builder, builder.fcmp_ordered( - "==" if inv else "!=", - self.auto_load(builder), - ll.Constant(self.get_llvm_type(), 0.0))) - return r - - def o_not(self, builder): - return self.o_bool(builder, True) - - def o_neg(self, builder): - r = VFloat() - if builder is not None: - r.auto_store( - builder, builder.fmul( - self.auto_load(builder), - ll.Constant(self.get_llvm_type(), -1.0))) - return r - - def o_intx(self, target_bits, builder): - r = VInt(target_bits) - if builder is not None: - r.auto_store(builder, builder.fptosi(self.auto_load(builder), - r.get_llvm_type())) - return r - - def o_roundx(self, target_bits, builder): - r = VInt(target_bits) - if builder is not None: - function = builder.basic_block.function - neg_block = function.append_basic_block("fr_neg") - merge_block = function.append_basic_block("fr_merge") - - half = VFloat() - half.alloca(builder, "half") - half.set_const_value(builder, 0.5) - - condition = builder.fcmp_ordered( - "<", - self.auto_load(builder), - ll.Constant(self.get_llvm_type(), 0.0)) - builder.cbranch(condition, neg_block, merge_block) - - builder.position_at_end(neg_block) - half.set_const_value(builder, -0.5) - builder.branch(merge_block) - - builder.position_at_end(merge_block) - s = builder.fadd(self.auto_load(builder), half.auto_load(builder)) - r.auto_store(builder, builder.fptosi(s, r.get_llvm_type())) - return r - - def o_floordiv(self, other, builder): - return self.o_truediv(other, builder).o_int64(builder).o_float(builder) - -def _make_vfloat_binop_method(builder_name, reverse): - def binop_method(self, other, builder): - if not hasattr(other, "o_float"): - return NotImplemented - r = VFloat() - if builder is not None: - left = self.o_float(builder) - right = other.o_float(builder) - if reverse: - left, right = right, left - bf = getattr(builder, builder_name) - r.auto_store( - builder, bf(left.auto_load(builder), - right.auto_load(builder))) - return r - return binop_method - -for _method_name, _builder_name in (("add", "fadd"), - ("sub", "fsub"), - ("mul", "fmul"), - ("truediv", "fdiv")): - setattr(VFloat, "o_" + _method_name, - _make_vfloat_binop_method(_builder_name, False)) - setattr(VFloat, "or_" + _method_name, - _make_vfloat_binop_method(_builder_name, True)) - - -def _make_vfloat_cmp_method(fcmp_val): - def cmp_method(self, other, builder): - if not hasattr(other, "o_float"): - return NotImplemented - r = VBool() - if builder is not None: - left = self.o_float(builder) - right = other.o_float(builder) - r.auto_store( - builder, - builder.fcmp_ordered( - fcmp_val, left.auto_load(builder), - right.auto_load(builder))) - return r - return cmp_method - -for _method_name, _fcmp_val in (("o_eq", "=="), - ("o_ne", "!="), - ("o_lt", "<"), - ("o_le", "<="), - ("o_gt", ">"), - ("o_ge", ">=")): - setattr(VFloat, _method_name, _make_vfloat_cmp_method(_fcmp_val)) diff --git a/artiq/py2llvm_old/infer_types.py b/artiq/py2llvm_old/infer_types.py deleted file mode 100644 index 7de53bab8..000000000 --- a/artiq/py2llvm_old/infer_types.py +++ /dev/null @@ -1,75 +0,0 @@ -import pythonparser.algorithm -from pythonparser import ast -from copy import deepcopy - -from artiq.py2llvm.ast_body import Visitor -from artiq.py2llvm import base_types - - -class _TypeScanner(pythonparser.algorithm.Visitor): - def __init__(self, env, ns): - self.exprv = Visitor(env, ns) - - def _update_target(self, target, val): - ns = self.exprv.ns - if isinstance(target, ast.Name): - if target.id in ns: - ns[target.id].merge(val) - else: - ns[target.id] = deepcopy(val) - elif isinstance(target, ast.Subscript): - target = target.value - levels = 0 - while isinstance(target, ast.Subscript): - target = target.value - levels += 1 - if isinstance(target, ast.Name): - target_value = ns[target.id] - for i in range(levels): - target_value = target_value.o_subscript(None, None) - target_value.merge_subscript(val) - else: - raise NotImplementedError - else: - raise NotImplementedError - - def visit_Assign(self, node): - val = self.exprv.visit_expression(node.value) - for target in node.targets: - self._update_target(target, val) - - def visit_AugAssign(self, node): - val = self.exprv.visit_expression(ast.BinOp( - op=node.op, left=node.target, right=node.value)) - self._update_target(node.target, val) - - def visit_For(self, node): - it = self.exprv.visit_expression(node.iter) - self._update_target(node.target, it.get_value_ptr()) - self.generic_visit(node) - - def visit_Return(self, node): - if node.value is None: - val = base_types.VNone() - else: - val = self.exprv.visit_expression(node.value) - ns = self.exprv.ns - if "return" in ns: - ns["return"].merge(val) - else: - ns["return"] = deepcopy(val) - - -def infer_function_types(env, node, param_types): - ns = deepcopy(param_types) - ts = _TypeScanner(env, ns) - ts.visit(node) - while True: - prev_ns = deepcopy(ns) - ts = _TypeScanner(env, ns) - ts.visit(node) - if all(v.same_type(prev_ns[k]) for k, v in ns.items()): - # no more promotions - completed - if "return" not in ns: - ns["return"] = base_types.VNone() - return ns diff --git a/artiq/py2llvm_old/iterators.py b/artiq/py2llvm_old/iterators.py deleted file mode 100644 index 0e1526319..000000000 --- a/artiq/py2llvm_old/iterators.py +++ /dev/null @@ -1,51 +0,0 @@ -from artiq.py2llvm.values import operators -from artiq.py2llvm.base_types import VInt - -class IRange: - def __init__(self, builder, args): - minimum, step = None, None - if len(args) == 1: - maximum = args[0] - elif len(args) == 2: - minimum, maximum = args - else: - minimum, maximum, step = args - if minimum is None: - minimum = VInt() - if builder is not None: - minimum.set_const_value(builder, 0) - if step is None: - step = VInt() - if builder is not None: - step.set_const_value(builder, 1) - - self._counter = minimum.new() - self._counter.merge(maximum) - self._counter.merge(step) - self._minimum = self._counter.new() - self._maximum = self._counter.new() - self._step = self._counter.new() - - if builder is not None: - self._minimum.alloca(builder, "irange_min") - self._maximum.alloca(builder, "irange_max") - self._step.alloca(builder, "irange_step") - self._counter.alloca(builder, "irange_count") - - self._minimum.set_value(builder, minimum) - self._maximum.set_value(builder, maximum) - self._step.set_value(builder, step) - - counter_init = operators.sub(self._minimum, self._step, builder) - self._counter.set_value(builder, counter_init) - - # must be a pointer value that can be dereferenced anytime - # to get the current value of the iterator - def get_value_ptr(self): - return self._counter - - def o_next(self, builder): - self._counter.set_value( - builder, - operators.add(self._counter, self._step, builder)) - return operators.lt(self._counter, self._maximum, builder) diff --git a/artiq/py2llvm_old/lists.py b/artiq/py2llvm_old/lists.py deleted file mode 100644 index e17ab5348..000000000 --- a/artiq/py2llvm_old/lists.py +++ /dev/null @@ -1,72 +0,0 @@ -import llvmlite_or1k.ir as ll - -from artiq.py2llvm.values import VGeneric -from artiq.py2llvm.base_types import VInt, VNone - - -class VList(VGeneric): - def __init__(self, el_type, alloc_count): - VGeneric.__init__(self) - self.el_type = el_type - self.alloc_count = alloc_count - - def get_llvm_type(self): - count = 0 if self.alloc_count is None else self.alloc_count - if isinstance(self.el_type, VNone): - return ll.LiteralStructType([ll.IntType(32)]) - else: - return ll.LiteralStructType([ - ll.IntType(32), ll.ArrayType(self.el_type.get_llvm_type(), - count)]) - - def __repr__(self): - return "".format( - repr(self.el_type), - "?" if self.alloc_count is None else self.alloc_count) - - def same_type(self, other): - return (isinstance(other, VList) - and self.el_type.same_type(other.el_type)) - - def merge(self, other): - if isinstance(other, VList): - if self.alloc_count: - if other.alloc_count: - self.el_type.merge(other.el_type) - if self.alloc_count < other.alloc_count: - self.alloc_count = other.alloc_count - else: - self.el_type = other.el_type.new() - self.alloc_count = other.alloc_count - else: - raise TypeError("Incompatible types: {} and {}" - .format(repr(self), repr(other))) - - def merge_subscript(self, other): - self.el_type.merge(other) - - def set_count(self, builder, count): - count_ptr = builder.gep(self.llvm_value, [ - ll.Constant(ll.IntType(32), 0), - ll.Constant(ll.IntType(32), 0)]) - builder.store(ll.Constant(ll.IntType(32), count), count_ptr) - - def o_len(self, builder): - r = VInt() - if builder is not None: - count_ptr = builder.gep(self.llvm_value, [ - ll.Constant(ll.IntType(32), 0), - ll.Constant(ll.IntType(32), 0)]) - r.auto_store(builder, builder.load(count_ptr)) - return r - - def o_subscript(self, index, builder): - r = self.el_type.new() - if builder is not None and not isinstance(r, VNone): - index = index.o_int(builder).auto_load(builder) - ssa_r = builder.gep(self.llvm_value, [ - ll.Constant(ll.IntType(32), 0), - ll.Constant(ll.IntType(32), 1), - index]) - r.auto_store(builder, ssa_r) - return r diff --git a/artiq/py2llvm_old/module.py b/artiq/py2llvm_old/module.py deleted file mode 100644 index f4df806e6..000000000 --- a/artiq/py2llvm_old/module.py +++ /dev/null @@ -1,62 +0,0 @@ -import llvmlite_or1k.ir as ll -import llvmlite_or1k.binding as llvm - -from artiq.py2llvm import infer_types, ast_body, base_types, fractions, tools - - -class Module: - def __init__(self, runtime=None): - self.llvm_module = ll.Module("main") - self.runtime = runtime - - if self.runtime is not None: - self.runtime.init_module(self) - fractions.init_module(self) - - def finalize(self): - self.llvm_module_ref = llvm.parse_assembly(str(self.llvm_module)) - pmb = llvm.create_pass_manager_builder() - pmb.opt_level = 2 - pm = llvm.create_module_pass_manager() - pmb.populate(pm) - pm.run(self.llvm_module_ref) - - def get_ee(self): - self.finalize() - tm = llvm.Target.from_default_triple().create_target_machine() - ee = llvm.create_mcjit_compiler(self.llvm_module_ref, tm) - ee.finalize_object() - return ee - - def emit_object(self): - self.finalize() - return self.runtime.emit_object() - - def compile_function(self, func_def, param_types): - ns = infer_types.infer_function_types(self.runtime, func_def, param_types) - retval = ns["return"] - - function_type = ll.FunctionType(retval.get_llvm_type(), - [ns[arg.arg].get_llvm_type() for arg in func_def.args.args]) - function = ll.Function(self.llvm_module, function_type, func_def.name) - bb = function.append_basic_block("entry") - builder = ll.IRBuilder() - builder.position_at_end(bb) - - for arg_ast, arg_llvm in zip(func_def.args.args, function.args): - arg_llvm.name = arg_ast.arg - for k, v in ns.items(): - v.alloca(builder, k) - for arg_ast, arg_llvm in zip(func_def.args.args, function.args): - ns[arg_ast.arg].auto_store(builder, arg_llvm) - - visitor = ast_body.Visitor(self.runtime, ns, builder) - visitor.visit_statements(func_def.body) - - if not tools.is_terminated(builder.basic_block): - if isinstance(retval, base_types.VNone): - builder.ret_void() - else: - builder.ret(retval.auto_load(builder)) - - return function, retval diff --git a/artiq/py2llvm_old/test/py2llvm.py b/artiq/py2llvm_old/test/py2llvm.py new file mode 100644 index 000000000..c6d9f0135 --- /dev/null +++ b/artiq/py2llvm_old/test/py2llvm.py @@ -0,0 +1,169 @@ +import unittest +from pythonparser import parse, ast +import inspect +from fractions import Fraction +from ctypes import CFUNCTYPE, c_int, c_int32, c_int64, c_double +import struct + +import llvmlite_or1k.binding as llvm + +from artiq.language.core import int64 +from artiq.py2llvm.infer_types import infer_function_types +from artiq.py2llvm import base_types, lists +from artiq.py2llvm.module import Module + +def simplify_encode(a, b): + f = Fraction(a, b) + return f.numerator*1000 + f.denominator + + +def frac_arith_encode(op, a, b, c, d): + if op == 0: + f = Fraction(a, b) - Fraction(c, d) + elif op == 1: + f = Fraction(a, b) + Fraction(c, d) + elif op == 2: + f = Fraction(a, b) * Fraction(c, d) + else: + f = Fraction(a, b) / Fraction(c, d) + return f.numerator*1000 + f.denominator + + +def frac_arith_encode_int(op, a, b, x): + if op == 0: + f = Fraction(a, b) - x + elif op == 1: + f = Fraction(a, b) + x + elif op == 2: + f = Fraction(a, b) * x + else: + f = Fraction(a, b) / x + return f.numerator*1000 + f.denominator + + +def frac_arith_encode_int_rev(op, a, b, x): + if op == 0: + f = x - Fraction(a, b) + elif op == 1: + f = x + Fraction(a, b) + elif op == 2: + f = x * Fraction(a, b) + else: + f = x / Fraction(a, b) + return f.numerator*1000 + f.denominator + + +def frac_arith_float(op, a, b, x): + if op == 0: + return Fraction(a, b) - x + elif op == 1: + return Fraction(a, b) + x + elif op == 2: + return Fraction(a, b) * x + else: + return Fraction(a, b) / x + + +def frac_arith_float_rev(op, a, b, x): + if op == 0: + return x - Fraction(a, b) + elif op == 1: + return x + Fraction(a, b) + elif op == 2: + return x * Fraction(a, b) + else: + return x / Fraction(a, b) + + +class CodeGenCase(unittest.TestCase): + def test_frac_simplify(self): + simplify_encode_c = CompiledFunction( + simplify_encode, {"a": base_types.VInt(), "b": base_types.VInt()}) + for a in _test_range(): + for b in _test_range(): + self.assertEqual( + simplify_encode_c(a, b), simplify_encode(a, b)) + + def _test_frac_arith(self, op): + frac_arith_encode_c = CompiledFunction( + frac_arith_encode, { + "op": base_types.VInt(), + "a": base_types.VInt(), "b": base_types.VInt(), + "c": base_types.VInt(), "d": base_types.VInt()}) + for a in _test_range(): + for b in _test_range(): + for c in _test_range(): + for d in _test_range(): + self.assertEqual( + frac_arith_encode_c(op, a, b, c, d), + frac_arith_encode(op, a, b, c, d)) + + def test_frac_add(self): + self._test_frac_arith(0) + + def test_frac_sub(self): + self._test_frac_arith(1) + + def test_frac_mul(self): + self._test_frac_arith(2) + + def test_frac_div(self): + self._test_frac_arith(3) + + def _test_frac_arith_int(self, op, rev): + f = frac_arith_encode_int_rev if rev else frac_arith_encode_int + f_c = CompiledFunction(f, { + "op": base_types.VInt(), + "a": base_types.VInt(), "b": base_types.VInt(), + "x": base_types.VInt()}) + for a in _test_range(): + for b in _test_range(): + for x in _test_range(): + self.assertEqual( + f_c(op, a, b, x), + f(op, a, b, x)) + + def test_frac_add_int(self): + self._test_frac_arith_int(0, False) + self._test_frac_arith_int(0, True) + + def test_frac_sub_int(self): + self._test_frac_arith_int(1, False) + self._test_frac_arith_int(1, True) + + def test_frac_mul_int(self): + self._test_frac_arith_int(2, False) + self._test_frac_arith_int(2, True) + + def test_frac_div_int(self): + self._test_frac_arith_int(3, False) + self._test_frac_arith_int(3, True) + + def _test_frac_arith_float(self, op, rev): + f = frac_arith_float_rev if rev else frac_arith_float + f_c = CompiledFunction(f, { + "op": base_types.VInt(), + "a": base_types.VInt(), "b": base_types.VInt(), + "x": base_types.VFloat()}) + for a in _test_range(): + for b in _test_range(): + for x in _test_range(): + self.assertAlmostEqual( + f_c(op, a, b, x/2), + f(op, a, b, x/2)) + + def test_frac_add_float(self): + self._test_frac_arith_float(0, False) + self._test_frac_arith_float(0, True) + + def test_frac_sub_float(self): + self._test_frac_arith_float(1, False) + self._test_frac_arith_float(1, True) + + def test_frac_mul_float(self): + self._test_frac_arith_float(2, False) + self._test_frac_arith_float(2, True) + + def test_frac_div_float(self): + self._test_frac_arith_float(3, False) + self._test_frac_arith_float(3, True) diff --git a/artiq/py2llvm_old/tools.py b/artiq/py2llvm_old/tools.py deleted file mode 100644 index ba9e76949..000000000 --- a/artiq/py2llvm_old/tools.py +++ /dev/null @@ -1,5 +0,0 @@ -import llvmlite_or1k.ir as ll - -def is_terminated(basic_block): - return (basic_block.instructions - and isinstance(basic_block.instructions[-1], ll.Terminator)) diff --git a/artiq/transforms/inline.py b/artiq/py2llvm_old/transforms/inline.py similarity index 100% rename from artiq/transforms/inline.py rename to artiq/py2llvm_old/transforms/inline.py diff --git a/artiq/transforms/interleave.py b/artiq/py2llvm_old/transforms/interleave.py similarity index 100% rename from artiq/transforms/interleave.py rename to artiq/py2llvm_old/transforms/interleave.py diff --git a/artiq/transforms/lower_time.py b/artiq/py2llvm_old/transforms/lower_time.py similarity index 100% rename from artiq/transforms/lower_time.py rename to artiq/py2llvm_old/transforms/lower_time.py diff --git a/artiq/transforms/quantize_time.py b/artiq/py2llvm_old/transforms/quantize_time.py similarity index 100% rename from artiq/transforms/quantize_time.py rename to artiq/py2llvm_old/transforms/quantize_time.py diff --git a/artiq/transforms/unroll_loops.py b/artiq/py2llvm_old/transforms/unroll_loops.py similarity index 100% rename from artiq/transforms/unroll_loops.py rename to artiq/py2llvm_old/transforms/unroll_loops.py diff --git a/artiq/py2llvm_old/values.py b/artiq/py2llvm_old/values.py deleted file mode 100644 index 6f0b90e2c..000000000 --- a/artiq/py2llvm_old/values.py +++ /dev/null @@ -1,94 +0,0 @@ -from types import SimpleNamespace -from copy import copy - -import llvmlite_or1k.ir as ll - - -class VGeneric: - def __init__(self): - self.llvm_value = None - - def new(self): - r = copy(self) - r.llvm_value = None - return r - - def __repr__(self): - return "<" + self.__class__.__name__ + ">" - - def same_type(self, other): - return isinstance(other, self.__class__) - - def merge(self, other): - if not self.same_type(other): - raise TypeError("Incompatible types: {} and {}" - .format(repr(self), repr(other))) - - def auto_load(self, builder): - if isinstance(self.llvm_value.type, ll.PointerType): - return builder.load(self.llvm_value) - else: - return self.llvm_value - - def auto_store(self, builder, llvm_value): - if self.llvm_value is None: - self.llvm_value = llvm_value - elif isinstance(self.llvm_value.type, ll.PointerType): - builder.store(llvm_value, self.llvm_value) - else: - raise RuntimeError( - "Attempted to set LLVM SSA value multiple times") - - def alloca(self, builder, name=""): - if self.llvm_value is not None: - raise RuntimeError("Attempted to alloca existing LLVM value "+name) - self.llvm_value = builder.alloca(self.get_llvm_type(), name=name) - - def o_int(self, builder): - return self.o_intx(32, builder) - - def o_int64(self, builder): - return self.o_intx(64, builder) - - def o_round(self, builder): - return self.o_roundx(32, builder) - - def o_round64(self, builder): - return self.o_roundx(64, builder) - - -def _make_binary_operator(op_name): - def op(l, r, builder): - try: - opf = getattr(l, "o_" + op_name) - except AttributeError: - result = NotImplemented - else: - result = opf(r, builder) - if result is NotImplemented: - try: - ropf = getattr(r, "or_" + op_name) - except AttributeError: - result = NotImplemented - else: - result = ropf(l, builder) - if result is NotImplemented: - raise TypeError( - "Unsupported operand types for {}: {} and {}" - .format(op_name, type(l).__name__, type(r).__name__)) - return result - return op - - -def _make_operators(): - d = dict() - for op_name in ("add", "sub", "mul", - "truediv", "floordiv", "mod", - "pow", "lshift", "rshift", "xor", - "eq", "ne", "lt", "le", "gt", "ge"): - d[op_name] = _make_binary_operator(op_name) - d["and_"] = _make_binary_operator("and") - d["or_"] = _make_binary_operator("or") - return SimpleNamespace(**d) - -operators = _make_operators() diff --git a/artiq/test/py2llvm.py b/artiq/test/py2llvm.py deleted file mode 100644 index 07250b7d1..000000000 --- a/artiq/test/py2llvm.py +++ /dev/null @@ -1,372 +0,0 @@ -import unittest -from pythonparser import parse, ast -import inspect -from fractions import Fraction -from ctypes import CFUNCTYPE, c_int, c_int32, c_int64, c_double -import struct - -import llvmlite_or1k.binding as llvm - -from artiq.language.core import int64 -from artiq.py2llvm.infer_types import infer_function_types -from artiq.py2llvm import base_types, lists -from artiq.py2llvm.module import Module - - -llvm.initialize() -llvm.initialize_native_target() -llvm.initialize_native_asmprinter() -if struct.calcsize("P") < 8: - from ctypes import _dlopen, RTLD_GLOBAL - _dlopen("libgcc_s.so", RTLD_GLOBAL) - - -def _base_types(choice): - a = 2 # promoted later to int64 - b = a + 1 # initially int32, becomes int64 after a is promoted - c = b//2 # initially int32, becomes int64 after b is promoted - d = 4 and 5 # stays int32 - x = int64(7) - a += x # promotes a to int64 - foo = True | True or False - bar = None - myf = 4.5 - myf2 = myf + x - - if choice and foo and not bar: - return d - elif myf2: - return x + c - else: - return int64(8) - - -def _build_function_types(f): - return infer_function_types( - None, parse(inspect.getsource(f)), - dict()) - - -class FunctionBaseTypesCase(unittest.TestCase): - def setUp(self): - self.ns = _build_function_types(_base_types) - - def test_simple_types(self): - self.assertIsInstance(self.ns["foo"], base_types.VBool) - self.assertIsInstance(self.ns["bar"], base_types.VNone) - self.assertIsInstance(self.ns["d"], base_types.VInt) - self.assertEqual(self.ns["d"].nbits, 32) - self.assertIsInstance(self.ns["x"], base_types.VInt) - self.assertEqual(self.ns["x"].nbits, 64) - self.assertIsInstance(self.ns["myf"], base_types.VFloat) - self.assertIsInstance(self.ns["myf2"], base_types.VFloat) - - def test_promotion(self): - for v in "abc": - self.assertIsInstance(self.ns[v], base_types.VInt) - self.assertEqual(self.ns[v].nbits, 64) - - def test_return(self): - self.assertIsInstance(self.ns["return"], base_types.VInt) - self.assertEqual(self.ns["return"].nbits, 64) - - -def test_list_types(): - a = [0, 0, 0, 0, 0] - for i in range(2): - a[i] = int64(8) - return a - - -class FunctionListTypesCase(unittest.TestCase): - def setUp(self): - self.ns = _build_function_types(test_list_types) - - def test_list_types(self): - self.assertIsInstance(self.ns["a"], lists.VList) - self.assertIsInstance(self.ns["a"].el_type, base_types.VInt) - self.assertEqual(self.ns["a"].el_type.nbits, 64) - self.assertEqual(self.ns["a"].alloc_count, 5) - self.assertIsInstance(self.ns["i"], base_types.VInt) - self.assertEqual(self.ns["i"].nbits, 32) - - -def _value_to_ctype(v): - if isinstance(v, base_types.VBool): - return c_int - elif isinstance(v, base_types.VInt): - if v.nbits == 32: - return c_int32 - elif v.nbits == 64: - return c_int64 - else: - raise NotImplementedError(str(v)) - elif isinstance(v, base_types.VFloat): - return c_double - else: - raise NotImplementedError(str(v)) - - -class CompiledFunction: - def __init__(self, function, param_types): - module = Module() - - func_def = parse(inspect.getsource(function)).body[0] - function, retval = module.compile_function(func_def, param_types) - argvals = [param_types[arg.arg] for arg in func_def.args.args] - - ee = module.get_ee() - cfptr = ee.get_pointer_to_global( - module.llvm_module_ref.get_function(function.name)) - retval_ctype = _value_to_ctype(retval) - argval_ctypes = [_value_to_ctype(argval) for argval in argvals] - self.cfunc = CFUNCTYPE(retval_ctype, *argval_ctypes)(cfptr) - - # HACK: prevent garbage collection of self.cfunc internals - self.ee = ee - - def __call__(self, *args): - return self.cfunc(*args) - - -def arith(op, a, b): - if op == 0: - return a + b - elif op == 1: - return a - b - elif op == 2: - return a * b - else: - return a / b - - -def is_prime(x): - d = 2 - while d*d <= x: - if not x % d: - return False - d += 1 - return True - - -def simplify_encode(a, b): - f = Fraction(a, b) - return f.numerator*1000 + f.denominator - - -def frac_arith_encode(op, a, b, c, d): - if op == 0: - f = Fraction(a, b) - Fraction(c, d) - elif op == 1: - f = Fraction(a, b) + Fraction(c, d) - elif op == 2: - f = Fraction(a, b) * Fraction(c, d) - else: - f = Fraction(a, b) / Fraction(c, d) - return f.numerator*1000 + f.denominator - - -def frac_arith_encode_int(op, a, b, x): - if op == 0: - f = Fraction(a, b) - x - elif op == 1: - f = Fraction(a, b) + x - elif op == 2: - f = Fraction(a, b) * x - else: - f = Fraction(a, b) / x - return f.numerator*1000 + f.denominator - - -def frac_arith_encode_int_rev(op, a, b, x): - if op == 0: - f = x - Fraction(a, b) - elif op == 1: - f = x + Fraction(a, b) - elif op == 2: - f = x * Fraction(a, b) - else: - f = x / Fraction(a, b) - return f.numerator*1000 + f.denominator - - -def frac_arith_float(op, a, b, x): - if op == 0: - return Fraction(a, b) - x - elif op == 1: - return Fraction(a, b) + x - elif op == 2: - return Fraction(a, b) * x - else: - return Fraction(a, b) / x - - -def frac_arith_float_rev(op, a, b, x): - if op == 0: - return x - Fraction(a, b) - elif op == 1: - return x + Fraction(a, b) - elif op == 2: - return x * Fraction(a, b) - else: - return x / Fraction(a, b) - - -def list_test(): - x = 80 - a = [3 for x in range(7)] - b = [1, 2, 4, 5, 4, 0, 5] - a[3] = x - a[0] += 6 - a[1] = b[1] + b[2] - - acc = 0 - for i in range(7): - if i and a[i]: - acc += 1 - acc += a[i] - return acc - - -def corner_cases(): - two = True + True - (not True) - three = two + True//True - False*True - two_float = three - True/True - one_float = two_float - (1.0 == bool(0.1)) - zero = int(one_float) + round(-0.6) - eleven_float = zero + 5.5//0.5 - ten_float = eleven_float + round(Fraction(2, -3)) - return ten_float - - -def _test_range(): - for i in range(5, 10): - yield i - yield -i - - -class CodeGenCase(unittest.TestCase): - def _test_float_arith(self, op): - arith_c = CompiledFunction(arith, { - "op": base_types.VInt(), - "a": base_types.VFloat(), "b": base_types.VFloat()}) - for a in _test_range(): - for b in _test_range(): - self.assertEqual(arith_c(op, a/2, b/2), arith(op, a/2, b/2)) - - def test_float_add(self): - self._test_float_arith(0) - - def test_float_sub(self): - self._test_float_arith(1) - - def test_float_mul(self): - self._test_float_arith(2) - - def test_float_div(self): - self._test_float_arith(3) - - def test_is_prime(self): - is_prime_c = CompiledFunction(is_prime, {"x": base_types.VInt()}) - for i in range(200): - self.assertEqual(is_prime_c(i), is_prime(i)) - - def test_frac_simplify(self): - simplify_encode_c = CompiledFunction( - simplify_encode, {"a": base_types.VInt(), "b": base_types.VInt()}) - for a in _test_range(): - for b in _test_range(): - self.assertEqual( - simplify_encode_c(a, b), simplify_encode(a, b)) - - def _test_frac_arith(self, op): - frac_arith_encode_c = CompiledFunction( - frac_arith_encode, { - "op": base_types.VInt(), - "a": base_types.VInt(), "b": base_types.VInt(), - "c": base_types.VInt(), "d": base_types.VInt()}) - for a in _test_range(): - for b in _test_range(): - for c in _test_range(): - for d in _test_range(): - self.assertEqual( - frac_arith_encode_c(op, a, b, c, d), - frac_arith_encode(op, a, b, c, d)) - - def test_frac_add(self): - self._test_frac_arith(0) - - def test_frac_sub(self): - self._test_frac_arith(1) - - def test_frac_mul(self): - self._test_frac_arith(2) - - def test_frac_div(self): - self._test_frac_arith(3) - - def _test_frac_arith_int(self, op, rev): - f = frac_arith_encode_int_rev if rev else frac_arith_encode_int - f_c = CompiledFunction(f, { - "op": base_types.VInt(), - "a": base_types.VInt(), "b": base_types.VInt(), - "x": base_types.VInt()}) - for a in _test_range(): - for b in _test_range(): - for x in _test_range(): - self.assertEqual( - f_c(op, a, b, x), - f(op, a, b, x)) - - def test_frac_add_int(self): - self._test_frac_arith_int(0, False) - self._test_frac_arith_int(0, True) - - def test_frac_sub_int(self): - self._test_frac_arith_int(1, False) - self._test_frac_arith_int(1, True) - - def test_frac_mul_int(self): - self._test_frac_arith_int(2, False) - self._test_frac_arith_int(2, True) - - def test_frac_div_int(self): - self._test_frac_arith_int(3, False) - self._test_frac_arith_int(3, True) - - def _test_frac_arith_float(self, op, rev): - f = frac_arith_float_rev if rev else frac_arith_float - f_c = CompiledFunction(f, { - "op": base_types.VInt(), - "a": base_types.VInt(), "b": base_types.VInt(), - "x": base_types.VFloat()}) - for a in _test_range(): - for b in _test_range(): - for x in _test_range(): - self.assertAlmostEqual( - f_c(op, a, b, x/2), - f(op, a, b, x/2)) - - def test_frac_add_float(self): - self._test_frac_arith_float(0, False) - self._test_frac_arith_float(0, True) - - def test_frac_sub_float(self): - self._test_frac_arith_float(1, False) - self._test_frac_arith_float(1, True) - - def test_frac_mul_float(self): - self._test_frac_arith_float(2, False) - self._test_frac_arith_float(2, True) - - def test_frac_div_float(self): - self._test_frac_arith_float(3, False) - self._test_frac_arith_float(3, True) - - def test_list(self): - list_test_c = CompiledFunction(list_test, dict()) - self.assertEqual(list_test_c(), list_test()) - - def test_corner_cases(self): - corner_cases_c = CompiledFunction(corner_cases, dict()) - self.assertEqual(corner_cases_c(), corner_cases()) diff --git a/artiq/test/transforms.py b/artiq/test/transforms.py deleted file mode 100644 index dffee41a2..000000000 --- a/artiq/test/transforms.py +++ /dev/null @@ -1,44 +0,0 @@ -import unittest -import ast - -from artiq import ns -from artiq.coredevice import comm_dummy, core -from artiq.transforms.unparse import unparse - - -optimize_in = """ - -def run(): - dds_sysclk = Fraction(1000000000, 1) - n = seconds_to_mu((1.2345 * Fraction(1, 1000000000))) - with sequential: - frequency = 345 * Fraction(1000000, 1) - frequency_to_ftw_return = int((((2 ** 32) * frequency) / dds_sysclk)) - ftw = frequency_to_ftw_return - with sequential: - ftw2 = ftw - ftw_to_frequency_return = ((ftw2 * dds_sysclk) / (2 ** 32)) - f = ftw_to_frequency_return - phi = ((1000 * mu_to_seconds(n)) * f) - do_something(int(phi)) -""" - -optimize_out = """ - -def run(): - now = syscall('now_init') - try: - do_something(344) - finally: - syscall('now_save', now) -""" - - -class OptimizeCase(unittest.TestCase): - def test_optimize(self): - dmgr = dict() - dmgr["comm"] = comm_dummy.Comm(dmgr) - coredev = core.Core(dmgr, ref_period=1*ns) - func_def = ast.parse(optimize_in).body[0] - coredev.transform_stack(func_def, dict(), dict()) - self.assertEqual(unparse(func_def), optimize_out) diff --git a/artiq/transforms/__init__.py b/artiq/transforms/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/artiq/transforms/fold_constants.py b/artiq/transforms/fold_constants.py deleted file mode 100644 index 402fc243b..000000000 --- a/artiq/transforms/fold_constants.py +++ /dev/null @@ -1,156 +0,0 @@ -import ast -import operator -from fractions import Fraction - -from artiq.transforms.tools import * -from artiq.language.core import int64, round64 - - -_ast_unops = { - ast.Invert: operator.inv, - ast.Not: operator.not_, - ast.UAdd: operator.pos, - ast.USub: operator.neg -} - - -_ast_binops = { - ast.Add: operator.add, - ast.Sub: operator.sub, - ast.Mult: operator.mul, - ast.Div: operator.truediv, - ast.FloorDiv: operator.floordiv, - ast.Mod: operator.mod, - ast.Pow: operator.pow, - ast.LShift: operator.lshift, - ast.RShift: operator.rshift, - ast.BitOr: operator.or_, - ast.BitXor: operator.xor, - ast.BitAnd: operator.and_ -} - -_ast_cmpops = { - ast.Eq: operator.eq, - ast.NotEq: operator.ne, - ast.Lt: operator.lt, - ast.LtE: operator.le, - ast.Gt: operator.gt, - ast.GtE: operator.ge -} - -_ast_boolops = { - ast.Or: lambda x, y: x or y, - ast.And: lambda x, y: x and y -} - - -class _ConstantFolder(ast.NodeTransformer): - def visit_UnaryOp(self, node): - self.generic_visit(node) - try: - operand = eval_constant(node.operand) - except NotConstant: - return node - try: - op = _ast_unops[type(node.op)] - except KeyError: - return node - try: - result = value_to_ast(op(operand)) - except: - return node - return ast.copy_location(result, node) - - def visit_BinOp(self, node): - self.generic_visit(node) - try: - left, right = eval_constant(node.left), eval_constant(node.right) - except NotConstant: - return node - try: - op = _ast_binops[type(node.op)] - except KeyError: - return node - try: - result = value_to_ast(op(left, right)) - except: - return node - return ast.copy_location(result, node) - - def visit_Compare(self, node): - self.generic_visit(node) - try: - operands = [eval_constant(node.left)] - except NotConstant: - operands = [node.left] - ops = [] - for op, right_ast in zip(node.ops, node.comparators): - try: - right = eval_constant(right_ast) - except NotConstant: - right = right_ast - if (not isinstance(operands[-1], ast.AST) - and not isinstance(right, ast.AST)): - left = operands.pop() - operands.append(_ast_cmpops[type(op)](left, right)) - else: - ops.append(op) - operands.append(right_ast) - operands = [operand if isinstance(operand, ast.AST) - else ast.copy_location(value_to_ast(operand), node) - for operand in operands] - if len(operands) == 1: - return operands[0] - else: - node.left = operands[0] - node.right = operands[1:] - node.ops = ops - return node - - def visit_BoolOp(self, node): - self.generic_visit(node) - new_values = [] - for value in node.values: - try: - value_c = eval_constant(value) - except NotConstant: - new_values.append(value) - else: - if new_values and not isinstance(new_values[-1], ast.AST): - op = _ast_boolops[type(node.op)] - new_values[-1] = op(new_values[-1], value_c) - else: - new_values.append(value_c) - new_values = [v if isinstance(v, ast.AST) else value_to_ast(v) - for v in new_values] - if len(new_values) > 1: - node.values = new_values - return node - else: - return new_values[0] - - def visit_Call(self, node): - self.generic_visit(node) - fn = node.func.id - constant_ops = { - "int": int, - "int64": int64, - "round": round, - "round64": round64, - "Fraction": Fraction - } - if fn in constant_ops: - args = [] - for arg in node.args: - try: - args.append(eval_constant(arg)) - except NotConstant: - return node - result = value_to_ast(constant_ops[fn](*args)) - return ast.copy_location(result, node) - else: - return node - - -def fold_constants(node): - _ConstantFolder().visit(node) diff --git a/artiq/transforms/remove_dead_code.py b/artiq/transforms/remove_dead_code.py deleted file mode 100644 index 9a58c851d..000000000 --- a/artiq/transforms/remove_dead_code.py +++ /dev/null @@ -1,59 +0,0 @@ -import ast - -from artiq.transforms.tools import is_ref_transparent - - -class _SourceLister(ast.NodeVisitor): - def __init__(self): - self.sources = set() - - def visit_Name(self, node): - if isinstance(node.ctx, ast.Load): - self.sources.add(node.id) - - -class _DeadCodeRemover(ast.NodeTransformer): - def __init__(self, kept_targets): - self.kept_targets = kept_targets - - def visit_Assign(self, node): - new_targets = [] - for target in node.targets: - if (not isinstance(target, ast.Name) - or target.id in self.kept_targets): - new_targets.append(target) - if not new_targets and is_ref_transparent(node.value)[0]: - return None - else: - return node - - def visit_AugAssign(self, node): - if (isinstance(node.target, ast.Name) - and node.target.id not in self.kept_targets - and is_ref_transparent(node.value)[0]): - return None - else: - return node - - def visit_If(self, node): - self.generic_visit(node) - if isinstance(node.test, ast.NameConstant): - if node.test.value: - return node.body - else: - return node.orelse - else: - return node - - def visit_While(self, node): - self.generic_visit(node) - if isinstance(node.test, ast.NameConstant) and not node.test.value: - return node.orelse - else: - return node - - -def remove_dead_code(func_def): - sl = _SourceLister() - sl.visit(func_def) - _DeadCodeRemover(sl.sources).visit(func_def) diff --git a/artiq/transforms/remove_inter_assigns.py b/artiq/transforms/remove_inter_assigns.py deleted file mode 100644 index 56d877215..000000000 --- a/artiq/transforms/remove_inter_assigns.py +++ /dev/null @@ -1,149 +0,0 @@ -import ast -from copy import copy, deepcopy -from collections import defaultdict - -from artiq.transforms.tools import is_ref_transparent, count_all_nodes - - -class _TargetLister(ast.NodeVisitor): - def __init__(self): - self.targets = set() - - def visit_Name(self, node): - if isinstance(node.ctx, ast.Store): - self.targets.add(node.id) - - -class _InterAssignRemover(ast.NodeTransformer): - def __init__(self): - self.replacements = dict() - self.modified_names = set() - # name -> set of names that depend on it - # i.e. when x is modified, dependencies[x] is the set of names that - # cannot be replaced anymore - self.dependencies = defaultdict(set) - - def invalidate(self, name): - try: - del self.replacements[name] - except KeyError: - pass - for d in self.dependencies[name]: - self.invalidate(d) - del self.dependencies[name] - - def visit_Name(self, node): - if isinstance(node.ctx, ast.Load): - try: - return deepcopy(self.replacements[node.id]) - except KeyError: - return node - else: - self.modified_names.add(node.id) - self.invalidate(node.id) - return node - - def visit_Assign(self, node): - node.value = self.visit(node.value) - node.targets = [self.visit(target) for target in node.targets] - rt, depends_on = is_ref_transparent(node.value) - if rt and count_all_nodes(node.value) < 100: - for target in node.targets: - if isinstance(target, ast.Name): - if target.id not in depends_on: - self.replacements[target.id] = node.value - for d in depends_on: - self.dependencies[d].add(target.id) - return node - - def visit_AugAssign(self, node): - left = deepcopy(node.target) - left.ctx = ast.Load() - newnode = ast.copy_location( - ast.Assign( - targets=[node.target], - value=ast.BinOp(left=left, op=node.op, right=node.value) - ), - node - ) - return self.visit_Assign(newnode) - - def modified_names_push(self): - prev_modified_names = self.modified_names - self.modified_names = set() - return prev_modified_names - - def modified_names_pop(self, prev_modified_names): - for name in self.modified_names: - self.invalidate(name) - self.modified_names |= prev_modified_names - - def visit_Try(self, node): - prev_modified_names = self.modified_names_push() - node.body = [self.visit(stmt) for stmt in node.body] - self.modified_names_pop(prev_modified_names) - - prev_modified_names = self.modified_names_push() - prev_replacements = self.replacements - for handler in node.handlers: - self.replacements = copy(prev_replacements) - handler.body = [self.visit(stmt) for stmt in handler.body] - self.replacements = copy(prev_replacements) - node.orelse = [self.visit(stmt) for stmt in node.orelse] - self.modified_names_pop(prev_modified_names) - - prev_modified_names = self.modified_names_push() - node.finalbody = [self.visit(stmt) for stmt in node.finalbody] - self.modified_names_pop(prev_modified_names) - return node - - def visit_If(self, node): - node.test = self.visit(node.test) - - prev_modified_names = self.modified_names_push() - - prev_replacements = self.replacements - self.replacements = copy(prev_replacements) - node.body = [self.visit(n) for n in node.body] - self.replacements = copy(prev_replacements) - node.orelse = [self.visit(n) for n in node.orelse] - self.replacements = prev_replacements - - self.modified_names_pop(prev_modified_names) - - return node - - def visit_loop(self, node): - prev_modified_names = self.modified_names_push() - prev_replacements = self.replacements - - self.replacements = copy(prev_replacements) - tl = _TargetLister() - for n in node.body: - tl.visit(n) - for name in tl.targets: - self.invalidate(name) - node.body = [self.visit(n) for n in node.body] - - self.replacements = copy(prev_replacements) - node.orelse = [self.visit(n) for n in node.orelse] - - self.replacements = prev_replacements - self.modified_names_pop(prev_modified_names) - - def visit_For(self, node): - prev_modified_names = self.modified_names_push() - node.target = self.visit(node.target) - self.modified_names_pop(prev_modified_names) - node.iter = self.visit(node.iter) - self.visit_loop(node) - return node - - def visit_While(self, node): - self.visit_loop(node) - node.test = self.visit(node.test) - return node - - -def remove_inter_assigns(func_def): - _InterAssignRemover().visit(func_def) diff --git a/artiq/transforms/tools.py b/artiq/transforms/tools.py deleted file mode 100644 index 97d596d2b..000000000 --- a/artiq/transforms/tools.py +++ /dev/null @@ -1,141 +0,0 @@ -import ast -from fractions import Fraction - -from artiq.language import core as core_language -from artiq.language import units - - -embeddable_funcs = ( - core_language.delay_mu, core_language.at_mu, core_language.now_mu, - core_language.delay, - core_language.seconds_to_mu, core_language.mu_to_seconds, - core_language.syscall, core_language.watchdog, - range, bool, int, float, round, len, - core_language.int64, core_language.round64, - Fraction, core_language.EncodedException -) -embeddable_func_names = {func.__name__ for func in embeddable_funcs} - - -def is_embeddable(func): - for ef in embeddable_funcs: - if func is ef: - return True - return False - - -def eval_ast(expr, symdict=dict()): - if not isinstance(expr, ast.Expression): - expr = ast.copy_location(ast.Expression(expr), expr) - ast.fix_missing_locations(expr) - code = compile(expr, "", "eval") - return eval(code, symdict) - - -class NotASTRepresentable(Exception): - pass - - -def value_to_ast(value): - if isinstance(value, core_language.int64): # must be before int - return ast.Call( - func=ast.Name("int64", ast.Load()), - args=[ast.Num(int(value))], - keywords=[], starargs=None, kwargs=None) - elif isinstance(value, bool) or value is None: - # must also be before int - # isinstance(True/False, int) == True - return ast.NameConstant(value) - elif isinstance(value, (int, float)): - return ast.Num(value) - elif isinstance(value, Fraction): - return ast.Call( - func=ast.Name("Fraction", ast.Load()), - args=[ast.Num(value.numerator), ast.Num(value.denominator)], - keywords=[], starargs=None, kwargs=None) - elif isinstance(value, str): - return ast.Str(value) - elif isinstance(value, list): - elts = [value_to_ast(elt) for elt in value] - return ast.List(elts, ast.Load()) - else: - for kg in core_language.kernel_globals: - if value is getattr(core_language, kg): - return ast.Name(kg, ast.Load()) - raise NotASTRepresentable(str(value)) - - -class NotConstant(Exception): - pass - - -def eval_constant(node): - if isinstance(node, ast.Num): - return node.n - elif isinstance(node, ast.Str): - return node.s - elif isinstance(node, ast.NameConstant): - return node.value - elif isinstance(node, ast.Call): - funcname = node.func.id - if funcname == "int64": - return core_language.int64(eval_constant(node.args[0])) - elif funcname == "Fraction": - numerator = eval_constant(node.args[0]) - denominator = eval_constant(node.args[1]) - return Fraction(numerator, denominator) - else: - raise NotConstant - else: - raise NotConstant - - -_replaceable_funcs = { - "bool", "int", "float", "round", - "int64", "round64", "Fraction", - "seconds_to_mu", "mu_to_seconds" -} - - -def _is_ref_transparent(dependencies, expr): - if isinstance(expr, (ast.NameConstant, ast.Num, ast.Str)): - return True - elif isinstance(expr, ast.Name): - dependencies.add(expr.id) - return True - elif isinstance(expr, ast.UnaryOp): - return _is_ref_transparent(dependencies, expr.operand) - elif isinstance(expr, ast.BinOp): - return (_is_ref_transparent(dependencies, expr.left) - and _is_ref_transparent(dependencies, expr.right)) - elif isinstance(expr, ast.BoolOp): - return all(_is_ref_transparent(dependencies, v) for v in expr.values) - elif isinstance(expr, ast.Call): - return (expr.func.id in _replaceable_funcs and - all(_is_ref_transparent(dependencies, arg) - for arg in expr.args)) - else: - return False - - -def is_ref_transparent(expr): - dependencies = set() - if _is_ref_transparent(dependencies, expr): - return True, dependencies - else: - return False, None - - -class _NodeCounter(ast.NodeVisitor): - def __init__(self): - self.count = 0 - - def generic_visit(self, node): - self.count += 1 - ast.NodeVisitor.generic_visit(self, node) - - -def count_all_nodes(node): - nc = _NodeCounter() - nc.visit(node) - return nc.count diff --git a/artiq/transforms/unparse.py b/artiq/transforms/unparse.py deleted file mode 100644 index cacd6e73b..000000000 --- a/artiq/transforms/unparse.py +++ /dev/null @@ -1,600 +0,0 @@ -import sys -import ast - - -# Large float and imaginary literals get turned into infinities in the AST. -# We unparse those infinities to INFSTR. -INFSTR = "1e" + repr(sys.float_info.max_10_exp + 1) - - -def _interleave(inter, f, seq): - """Call f on each item in seq, calling inter() in between. - """ - seq = iter(seq) - try: - f(next(seq)) - except StopIteration: - pass - else: - for x in seq: - inter() - f(x) - - -class _Unparser: - """Methods in this class recursively traverse an AST and - output source code for the abstract syntax; original formatting - is disregarded. """ - - def __init__(self, tree): - """Print the source for tree to the "result" string.""" - self.result = "" - self._indent = 0 - self.dispatch(tree) - self.result += "\n" - - def fill(self, text=""): - "Indent a piece of text, according to the current indentation level" - self.result += "\n"+" "*self._indent + text - - def write(self, text): - "Append a piece of text to the current line." - self.result += text - - def enter(self): - "Print ':', and increase the indentation." - self.write(":") - self._indent += 1 - - def leave(self): - "Decrease the indentation level." - self._indent -= 1 - - def dispatch(self, tree): - "Dispatcher function, dispatching tree type T to method _T." - if isinstance(tree, list): - for t in tree: - self.dispatch(t) - return - meth = getattr(self, "_"+tree.__class__.__name__) - meth(tree) - - # Unparsing methods - # - # There should be one method per concrete grammar type - # Constructors should be grouped by sum type. Ideally, - # this would follow the order in the grammar, but - # currently doesn't. - - def _Module(self, tree): - for stmt in tree.body: - self.dispatch(stmt) - - # stmt - def _Expr(self, tree): - self.fill() - self.dispatch(tree.value) - - def _Import(self, t): - self.fill("import ") - _interleave(lambda: self.write(", "), self.dispatch, t.names) - - def _ImportFrom(self, t): - self.fill("from ") - self.write("." * t.level) - if t.module: - self.write(t.module) - self.write(" import ") - _interleave(lambda: self.write(", "), self.dispatch, t.names) - - def _Assign(self, t): - self.fill() - for target in t.targets: - self.dispatch(target) - self.write(" = ") - self.dispatch(t.value) - - def _AugAssign(self, t): - self.fill() - self.dispatch(t.target) - self.write(" "+self.binop[t.op.__class__.__name__]+"= ") - self.dispatch(t.value) - - def _Return(self, t): - self.fill("return") - if t.value: - self.write(" ") - self.dispatch(t.value) - - def _Pass(self, t): - self.fill("pass") - - def _Break(self, t): - self.fill("break") - - def _Continue(self, t): - self.fill("continue") - - def _Delete(self, t): - self.fill("del ") - _interleave(lambda: self.write(", "), self.dispatch, t.targets) - - def _Assert(self, t): - self.fill("assert ") - self.dispatch(t.test) - if t.msg: - self.write(", ") - self.dispatch(t.msg) - - def _Global(self, t): - self.fill("global ") - _interleave(lambda: self.write(", "), self.write, t.names) - - def _Nonlocal(self, t): - self.fill("nonlocal ") - _interleave(lambda: self.write(", "), self.write, t.names) - - def _Yield(self, t): - self.write("(") - self.write("yield") - if t.value: - self.write(" ") - self.dispatch(t.value) - self.write(")") - - def _YieldFrom(self, t): - self.write("(") - self.write("yield from") - if t.value: - self.write(" ") - self.dispatch(t.value) - self.write(")") - - def _Raise(self, t): - self.fill("raise") - if not t.exc: - assert not t.cause - return - self.write(" ") - self.dispatch(t.exc) - if t.cause: - self.write(" from ") - self.dispatch(t.cause) - - def _Try(self, t): - self.fill("try") - self.enter() - self.dispatch(t.body) - self.leave() - for ex in t.handlers: - self.dispatch(ex) - if t.orelse: - self.fill("else") - self.enter() - self.dispatch(t.orelse) - self.leave() - if t.finalbody: - self.fill("finally") - self.enter() - self.dispatch(t.finalbody) - self.leave() - - def _ExceptHandler(self, t): - self.fill("except") - if t.type: - self.write(" ") - self.dispatch(t.type) - if t.name: - self.write(" as ") - self.write(t.name) - self.enter() - self.dispatch(t.body) - self.leave() - - def _ClassDef(self, t): - self.write("\n") - for deco in t.decorator_list: - self.fill("@") - self.dispatch(deco) - self.fill("class "+t.name) - self.write("(") - comma = False - for e in t.bases: - if comma: - self.write(", ") - else: - comma = True - self.dispatch(e) - for e in t.keywords: - if comma: - self.write(", ") - else: - comma = True - self.dispatch(e) - if t.starargs: - if comma: - self.write(", ") - else: - comma = True - self.write("*") - self.dispatch(t.starargs) - if t.kwargs: - if comma: - self.write(", ") - else: - comma = True - self.write("**") - self.dispatch(t.kwargs) - self.write(")") - - self.enter() - self.dispatch(t.body) - self.leave() - - def _FunctionDef(self, t): - self.write("\n") - for deco in t.decorator_list: - self.fill("@") - self.dispatch(deco) - self.fill("def "+t.name + "(") - self.dispatch(t.args) - self.write(")") - if t.returns: - self.write(" -> ") - self.dispatch(t.returns) - self.enter() - self.dispatch(t.body) - self.leave() - - def _For(self, t): - self.fill("for ") - self.dispatch(t.target) - self.write(" in ") - self.dispatch(t.iter) - self.enter() - self.dispatch(t.body) - self.leave() - if t.orelse: - self.fill("else") - self.enter() - self.dispatch(t.orelse) - self.leave() - - def _If(self, t): - self.fill("if ") - self.dispatch(t.test) - self.enter() - self.dispatch(t.body) - self.leave() - # collapse nested ifs into equivalent elifs. - while (t.orelse and len(t.orelse) == 1 and - isinstance(t.orelse[0], ast.If)): - t = t.orelse[0] - self.fill("elif ") - self.dispatch(t.test) - self.enter() - self.dispatch(t.body) - self.leave() - # final else - if t.orelse: - self.fill("else") - self.enter() - self.dispatch(t.orelse) - self.leave() - - def _While(self, t): - self.fill("while ") - self.dispatch(t.test) - self.enter() - self.dispatch(t.body) - self.leave() - if t.orelse: - self.fill("else") - self.enter() - self.dispatch(t.orelse) - self.leave() - - def _With(self, t): - self.fill("with ") - _interleave(lambda: self.write(", "), self.dispatch, t.items) - self.enter() - self.dispatch(t.body) - self.leave() - - # expr - def _Bytes(self, t): - self.write(repr(t.s)) - - def _Str(self, tree): - self.write(repr(tree.s)) - - def _Name(self, t): - self.write(t.id) - - def _NameConstant(self, t): - self.write(repr(t.value)) - - def _Num(self, t): - # Substitute overflowing decimal literal for AST infinities. - self.write(repr(t.n).replace("inf", INFSTR)) - - def _List(self, t): - self.write("[") - _interleave(lambda: self.write(", "), self.dispatch, t.elts) - self.write("]") - - def _ListComp(self, t): - self.write("[") - self.dispatch(t.elt) - for gen in t.generators: - self.dispatch(gen) - self.write("]") - - def _GeneratorExp(self, t): - self.write("(") - self.dispatch(t.elt) - for gen in t.generators: - self.dispatch(gen) - self.write(")") - - def _SetComp(self, t): - self.write("{") - self.dispatch(t.elt) - for gen in t.generators: - self.dispatch(gen) - self.write("}") - - def _DictComp(self, t): - self.write("{") - self.dispatch(t.key) - self.write(": ") - self.dispatch(t.value) - for gen in t.generators: - self.dispatch(gen) - self.write("}") - - def _comprehension(self, t): - self.write(" for ") - self.dispatch(t.target) - self.write(" in ") - self.dispatch(t.iter) - for if_clause in t.ifs: - self.write(" if ") - self.dispatch(if_clause) - - def _IfExp(self, t): - self.write("(") - self.dispatch(t.body) - self.write(" if ") - self.dispatch(t.test) - self.write(" else ") - self.dispatch(t.orelse) - self.write(")") - - def _Set(self, t): - assert(t.elts) # should be at least one element - self.write("{") - _interleave(lambda: self.write(", "), self.dispatch, t.elts) - self.write("}") - - def _Dict(self, t): - self.write("{") - - def write_pair(pair): - (k, v) = pair - self.dispatch(k) - self.write(": ") - self.dispatch(v) - _interleave(lambda: self.write(", "), write_pair, - zip(t.keys, t.values)) - self.write("}") - - def _Tuple(self, t): - self.write("(") - if len(t.elts) == 1: - (elt,) = t.elts - self.dispatch(elt) - self.write(",") - else: - _interleave(lambda: self.write(", "), self.dispatch, t.elts) - self.write(")") - - unop = {"Invert": "~", "Not": "not", "UAdd": "+", "USub": "-"} - - def _UnaryOp(self, t): - self.write("(") - self.write(self.unop[t.op.__class__.__name__]) - self.write(" ") - self.dispatch(t.operand) - self.write(")") - - binop = {"Add": "+", "Sub": "-", "Mult": "*", "Div": "/", "Mod": "%", - "LShift": "<<", "RShift": ">>", - "BitOr": "|", "BitXor": "^", "BitAnd": "&", - "FloorDiv": "//", "Pow": "**"} - - def _BinOp(self, t): - self.write("(") - self.dispatch(t.left) - self.write(" " + self.binop[t.op.__class__.__name__] + " ") - self.dispatch(t.right) - self.write(")") - - cmpops = {"Eq": "==", "NotEq": "!=", - "Lt": "<", "LtE": "<=", "Gt": ">", "GtE": ">=", - "Is": "is", "IsNot": "is not", "In": "in", "NotIn": "not in"} - - def _Compare(self, t): - self.write("(") - self.dispatch(t.left) - for o, e in zip(t.ops, t.comparators): - self.write(" " + self.cmpops[o.__class__.__name__] + " ") - self.dispatch(e) - self.write(")") - - boolops = {ast.And: "and", ast.Or: "or"} - - def _BoolOp(self, t): - self.write("(") - s = " %s " % self.boolops[t.op.__class__] - _interleave(lambda: self.write(s), self.dispatch, t.values) - self.write(")") - - def _Attribute(self, t): - self.dispatch(t.value) - # Special case: 3.__abs__() is a syntax error, so if t.value - # is an integer literal then we need to either parenthesize - # it or add an extra space to get 3 .__abs__(). - if isinstance(t.value, ast.Num) and isinstance(t.value.n, int): - self.write(" ") - self.write(".") - self.write(t.attr) - - def _Call(self, t): - self.dispatch(t.func) - self.write("(") - comma = False - for e in t.args: - if comma: - self.write(", ") - else: - comma = True - self.dispatch(e) - for e in t.keywords: - if comma: - self.write(", ") - else: - comma = True - self.dispatch(e) - if t.starargs: - if comma: - self.write(", ") - else: - comma = True - self.write("*") - self.dispatch(t.starargs) - if t.kwargs: - if comma: - self.write(", ") - else: - comma = True - self.write("**") - self.dispatch(t.kwargs) - self.write(")") - - def _Subscript(self, t): - self.dispatch(t.value) - self.write("[") - self.dispatch(t.slice) - self.write("]") - - def _Starred(self, t): - self.write("*") - self.dispatch(t.value) - - # slice - def _Ellipsis(self, t): - self.write("...") - - def _Index(self, t): - self.dispatch(t.value) - - def _Slice(self, t): - if t.lower: - self.dispatch(t.lower) - self.write(":") - if t.upper: - self.dispatch(t.upper) - if t.step: - self.write(":") - self.dispatch(t.step) - - def _ExtSlice(self, t): - _interleave(lambda: self.write(', '), self.dispatch, t.dims) - - # argument - def _arg(self, t): - self.write(t.arg) - if t.annotation: - self.write(": ") - self.dispatch(t.annotation) - - # others - def _arguments(self, t): - first = True - # normal arguments - defaults = [None] * (len(t.args) - len(t.defaults)) + t.defaults - for a, d in zip(t.args, defaults): - if first: - first = False - else: - self.write(", ") - self.dispatch(a) - if d: - self.write("=") - self.dispatch(d) - - # varargs, or bare '*' if no varargs but keyword-only arguments present - if t.vararg or t.kwonlyargs: - if first: - first = False - else: - self.write(", ") - self.write("*") - if t.vararg: - self.write(t.vararg.arg) - if t.vararg.annotation: - self.write(": ") - self.dispatch(t.vararg.annotation) - - # keyword-only arguments - if t.kwonlyargs: - for a, d in zip(t.kwonlyargs, t.kw_defaults): - if first: - first = False - else: - self.write(", ") - self.dispatch(a), - if d: - self.write("=") - self.dispatch(d) - - # kwargs - if t.kwarg: - if first: - first = False - else: - self.write(", ") - self.write("**"+t.kwarg.arg) - if t.kwarg.annotation: - self.write(": ") - self.dispatch(t.kwarg.annotation) - - def _keyword(self, t): - self.write(t.arg) - self.write("=") - self.dispatch(t.value) - - def _Lambda(self, t): - self.write("(") - self.write("lambda ") - self.dispatch(t.args) - self.write(": ") - self.dispatch(t.body) - self.write(")") - - def _alias(self, t): - self.write(t.name) - if t.asname: - self.write(" as "+t.asname) - - def _withitem(self, t): - self.dispatch(t.context_expr) - if t.optional_vars: - self.write(" as ") - self.dispatch(t.optional_vars) - - -def unparse(tree): - unparser = _Unparser(tree) - return unparser.result From 786fde827a9009866236cd4b38407537d6e0b2f5 Mon Sep 17 00:00:00 2001 From: whitequark Date: Tue, 11 Aug 2015 00:41:31 +0300 Subject: [PATCH 218/369] Unbreak tests. --- artiq/compiler/transforms/artiq_ir_generator.py | 2 +- artiq/compiler/transforms/inferencer.py | 2 +- artiq/compiler/transforms/llvm_ir_generator.py | 2 +- lit-test/test/inferencer/error_builtin_calls.py | 2 +- lit-test/test/inferencer/error_call.py | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/artiq/compiler/transforms/artiq_ir_generator.py b/artiq/compiler/transforms/artiq_ir_generator.py index b0ce78c91..a822f8815 100644 --- a/artiq/compiler/transforms/artiq_ir_generator.py +++ b/artiq/compiler/transforms/artiq_ir_generator.py @@ -186,7 +186,7 @@ class ARTIQIRGenerator(algorithm.Visitor): optargs.append(ir.Argument(ir.TOption(typ.optargs[arg_name]), "arg." + arg_name)) func = ir.Function(typ, ".".join(self.name), [env_arg] + args + optargs, - loc=node.keyword_loc) + loc=node.lambda_loc if is_lambda else node.keyword_loc) func.is_internal = is_internal self.functions.append(func) old_func, self.current_function = self.current_function, func diff --git a/artiq/compiler/transforms/inferencer.py b/artiq/compiler/transforms/inferencer.py index a256f9775..0fe000189 100644 --- a/artiq/compiler/transforms/inferencer.py +++ b/artiq/compiler/transforms/inferencer.py @@ -728,7 +728,7 @@ class Inferencer(algorithm.Visitor): elif keyword.arg in typ.optargs: self._unify(keyword.value.type, typ.optargs[keyword.arg], keyword.value.loc, None) - passed_args.add(keyword.arg) + passed_args[keyword.arg] = keyword.arg_loc for formalname in typ.args: if formalname not in passed_args: diff --git a/artiq/compiler/transforms/llvm_ir_generator.py b/artiq/compiler/transforms/llvm_ir_generator.py index 4eaf34e68..4fd341763 100644 --- a/artiq/compiler/transforms/llvm_ir_generator.py +++ b/artiq/compiler/transforms/llvm_ir_generator.py @@ -403,7 +403,7 @@ class LLVMIRGenerator: finally: self.llfunction = None self.llmap = {} - self.llphis = [] + self.phis = [] def process_Phi(self, insn): llinsn = self.llbuilder.phi(self.llty_of_type(insn.type), name=insn.name) diff --git a/lit-test/test/inferencer/error_builtin_calls.py b/lit-test/test/inferencer/error_builtin_calls.py index 32ce8b294..8eab7a203 100644 --- a/lit-test/test/inferencer/error_builtin_calls.py +++ b/lit-test/test/inferencer/error_builtin_calls.py @@ -11,5 +11,5 @@ len(1) # CHECK-L: ${LINE:+1}: error: the argument of list() must be of an iterable type list(1) -# CHECK-L: ${LINE:+1}: error: an argument of range() must be of a numeric type +# CHECK-L: ${LINE:+1}: error: an argument of range() must be of an integer type range([]) diff --git a/lit-test/test/inferencer/error_call.py b/lit-test/test/inferencer/error_call.py index 21ba5bdf6..739ffa249 100644 --- a/lit-test/test/inferencer/error_call.py +++ b/lit-test/test/inferencer/error_call.py @@ -13,7 +13,7 @@ f(*[]) # CHECK-L: ${LINE:+1}: error: variadic arguments are not supported f(**[]) -# CHECK-L: ${LINE:+1}: error: the argument 'x' is already passed +# 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 From fd3c8a28304bb62f03f0a240d0c54bd16f901f8b Mon Sep 17 00:00:00 2001 From: whitequark Date: Tue, 11 Aug 2015 12:50:49 +0300 Subject: [PATCH 219/369] language.core: remove {int,round}64, implement int with device semantics. --- artiq/language/core.py | 159 +++++++++++++++++++++++++++++------------ artiq/test/language.py | 44 ++++++++++++ 2 files changed, 159 insertions(+), 44 deletions(-) create mode 100644 artiq/test/language.py diff --git a/artiq/language/core.py b/artiq/language/core.py index 8ad224f51..e83c30de4 100644 --- a/artiq/language/core.py +++ b/artiq/language/core.py @@ -10,7 +10,7 @@ from functools import wraps from artiq.coredevice.runtime import source_loader -__all__ = ["int64", "round64", +__all__ = ["host_int", "int", "kernel", "portable", "syscall", "set_time_manager", "set_watchdog_factory", "ARTIQException"] @@ -24,64 +24,135 @@ kernel_globals = ( ) __all__.extend(kernel_globals) +host_int = int -class int64(int): - """64-bit integers for static compilation. +class int: + """ + Arbitrary-precision integers for static compilation. - When this class is used instead of Python's ``int``, the static compiler - stores the corresponding variable on 64 bits instead of 32. + The static compiler does not use unlimited-precision integers, + like Python normally does, because of their unbounded memory requirements. + Instead, it allows to choose a bit width (usually 32 or 64) at compile-time, + and all computations follow wrap-around semantics on overflow. - When used in the interpreter, it behaves as ``int`` and the results of - integer operations involving it are also ``int64`` (which matches the - size promotion rules of the static compiler). This way, it is possible to - specify 64-bit size annotations on constants that are passed to the - kernels. + This class implements the same semantics on the host. - Example: + For example: - >>> a = int64(1) - >>> b = int64(3) + 2 - >>> isinstance(a, int64) + >>> a = int(1, width=64) + >>> b = int(3, width=64) + 2 + >>> isinstance(a, int) True - >>> isinstance(b, int64) + >>> isinstance(b, int) True >>> a + b - 6 + int(6, width=64) + >>> int(10, width=32) + 0x7fffffff + int(9, width=32) + >>> int(0x80000000) + int(-2147483648, width=32) """ - pass -def _make_int64_op_method(int_method): - def method(self, *args): - r = int_method(self, *args) - if isinstance(r, int): - r = int64(r) - return r - return method + __slots__ = ['_value', '_width'] -for _op_name in ("neg", "pos", "abs", "invert", "round", - "add", "radd", "sub", "rsub", "mul", "rmul", "pow", "rpow", - "lshift", "rlshift", "rshift", "rrshift", - "and", "rand", "xor", "rxor", "or", "ror", - "floordiv", "rfloordiv", "mod", "rmod"): - _method_name = "__" + _op_name + "__" - _orig_method = getattr(int, _method_name) - setattr(int64, _method_name, _make_int64_op_method(_orig_method)) + def __new__(cls, value, width=32): + if isinstance(value, int): + return value + else: + sign_bit = 2 ** (width - 1) + value = host_int(value) + if value & sign_bit: + value = -1 & ~sign_bit + (value & (sign_bit - 1)) + 1 + else: + value &= sign_bit - 1 -for _op_name in ("add", "sub", "mul", "floordiv", "mod", - "pow", "lshift", "rshift", "lshift", - "and", "xor", "or"): - _op_method = getattr(int, "__" + _op_name + "__") - setattr(int64, "__i" + _op_name + "__", _make_int64_op_method(_op_method)) + self = super().__new__(cls) + self._value = value + self._width = width + return self + @property + def width(width): + return width._width -def round64(x): - """Rounds to a 64-bit integer. + def __int__(self): + return self._value - This function is equivalent to ``int64(round(x))`` but, when targeting - static compilation, prevents overflow when the rounded value is too large - to fit in a 32-bit integer. - """ - return int64(round(x)) + def __float__(self): + return float(self._value) + + def __str__(self): + return str(self._value) + + def __repr__(self): + return "int({}, width={})".format(self._value, self._width) + + def _unaryop(lower_fn): + def operator(self): + return int(lower_fn(self._value), self._width) + return operator + + __neg__ = _unaryop(host_int.__neg__) + __pos__ = _unaryop(host_int.__pos__) + __abs__ = _unaryop(host_int.__abs__) + __invert__ = _unaryop(host_int.__invert__) + __round__ = _unaryop(host_int.__round__) + + def _binaryop(lower_fn, rlower_fn=None): + def operator(self, other): + if isinstance(other, host_int): + return int(lower_fn(self._value, other), self._width) + elif isinstance(other, int): + width = self._width if self._width > other._width else other._width + return int(lower_fn(self._value, other._value), width) + elif rlower_fn: + return getattr(other, rlower_fn)(self._value) + else: + return NotImplemented + return operator + + __add__ = __iadd__ = _binaryop(host_int.__add__, "__radd__") + __sub__ = __isub__ = _binaryop(host_int.__sub__, "__rsub__") + __mul__ = __imul__ = _binaryop(host_int.__mul__, "__rmul__") + __floordiv__ = __ifloordiv__ = _binaryop(host_int.__floordiv__, "__rfloordiv__") + __mod__ = __imod__ = _binaryop(host_int.__mod__, "__rmod__") + __pow__ = __ipow__ = _binaryop(host_int.__pow__, "__rpow__") + + __radd__ = _binaryop(host_int.__radd__, "__add__") + __rsub__ = _binaryop(host_int.__rsub__, "__sub__") + __rmul__ = _binaryop(host_int.__rmul__, "__mul__") + __rfloordiv__ = _binaryop(host_int.__rfloordiv__, "__floordiv__") + __rmod__ = _binaryop(host_int.__rmod__, "__mod__") + __rpow__ = _binaryop(host_int.__rpow__, "__pow__") + + __lshift__ = __ilshift__ = _binaryop(host_int.__lshift__) + __rshift__ = __irshift__ = _binaryop(host_int.__rshift__) + __and__ = __iand__ = _binaryop(host_int.__and__) + __or__ = __ior__ = _binaryop(host_int.__or__) + __xor__ = __ixor__ = _binaryop(host_int.__xor__) + + __rlshift__ = _binaryop(host_int.__rlshift__) + __rrshift__ = _binaryop(host_int.__rrshift__) + __rand__ = _binaryop(host_int.__rand__) + __ror__ = _binaryop(host_int.__ror__) + __rxor__ = _binaryop(host_int.__rxor__) + + def _compareop(lower_fn, rlower_fn): + def operator(self, other): + if isinstance(other, host_int): + return lower_fn(self._value, other) + elif isinstance(other, int): + return lower_fn(self._value, other._value) + else: + return getattr(other, rlower_fn)(self._value) + return operator + + __eq__ = _compareop(host_int.__eq__, "__ne__") + __ne__ = _compareop(host_int.__ne__, "__eq__") + __gt__ = _compareop(host_int.__gt__, "__le__") + __ge__ = _compareop(host_int.__ge__, "__lt__") + __lt__ = _compareop(host_int.__lt__, "__ge__") + __le__ = _compareop(host_int.__le__, "__gt__") _ARTIQEmbeddedInfo = namedtuple("_ARTIQEmbeddedInfo", diff --git a/artiq/test/language.py b/artiq/test/language.py new file mode 100644 index 000000000..103df4569 --- /dev/null +++ b/artiq/test/language.py @@ -0,0 +1,44 @@ +import unittest + +from artiq.language.core import * + + +class LanguageCoreTest(unittest.TestCase): + def test_unary(self): + self.assertEqual(int(10), +int(10)) + self.assertEqual(int(-10), -int(10)) + self.assertEqual(int(~10), ~int(10)) + self.assertEqual(int(10), round(int(10))) + + def test_arith(self): + self.assertEqual(int(9), int(4) + int(5)) + self.assertEqual(int(9), int(4) + 5) + self.assertEqual(int(9), 5 + int(4)) + + self.assertEqual(9.0, int(4) + 5.0) + self.assertEqual(9.0, 5.0 + int(4)) + + a = int(5) + a += int(2) + a += 2 + self.assertEqual(int(9), a) + + def test_compare(self): + self.assertTrue(int(9) > int(8)) + self.assertTrue(int(9) > 8) + self.assertTrue(int(9) > 8.0) + self.assertTrue(9 > int(8)) + self.assertTrue(9.0 > int(8)) + + def test_bitwise(self): + self.assertEqual(int(0x100), int(0x10) << int(4)) + self.assertEqual(int(0x100), int(0x10) << 4) + self.assertEqual(int(0x100), 0x10 << int(4)) + + def test_wraparound(self): + self.assertEqual(int(0xffffffff), int(-1)) + self.assertTrue(int(0x7fffffff) > int(1)) + self.assertTrue(int(0x80000000) < int(-1)) + + self.assertEqual(int(9), int(10) + int(0xffffffff)) + self.assertEqual(-1.0, float(int(0xfffffffe) + int(1))) From 00efc8c636e5a8f98441bfc4020db8dce1bae858 Mon Sep 17 00:00:00 2001 From: whitequark Date: Sat, 15 Aug 2015 09:45:16 -0400 Subject: [PATCH 220/369] Implement class definitions and class attribute access. --- artiq/compiler/asttyped.py | 6 +- artiq/compiler/builtins.py | 32 +++----- artiq/compiler/ir.py | 4 +- .../compiler/transforms/artiq_ir_generator.py | 43 ++++++++--- .../compiler/transforms/asttyped_rewriter.py | 75 ++++++++++++++++++- artiq/compiler/transforms/inferencer.py | 57 ++++++++------ .../compiler/transforms/llvm_ir_generator.py | 2 +- artiq/compiler/types.py | 24 +++++- artiq/compiler/validators/escape.py | 14 ++-- lit-test/test/inferencer/builtin_calls.py | 16 ++-- lit-test/test/inferencer/class.py | 14 ++++ lit-test/test/inferencer/error_class.py | 10 +++ .../test/inferencer/error_local_unbound.py | 2 +- lit-test/test/inferencer/exception.py | 2 +- lit-test/test/inferencer/unify.py | 10 +-- lit-test/test/integration/class.py | 12 +++ 16 files changed, 235 insertions(+), 88 deletions(-) create mode 100644 lit-test/test/inferencer/class.py create mode 100644 lit-test/test/inferencer/error_class.py create mode 100644 lit-test/test/integration/class.py diff --git a/artiq/compiler/asttyped.py b/artiq/compiler/asttyped.py index f15726579..c9e543ae2 100644 --- a/artiq/compiler/asttyped.py +++ b/artiq/compiler/asttyped.py @@ -25,15 +25,15 @@ class scoped(object): class argT(ast.arg, commontyped): pass -class ClassDefT(ast.ClassDef, scoped): - pass +class ClassDefT(ast.ClassDef): + _types = ("constructor_type",) class FunctionDefT(ast.FunctionDef, scoped): _types = ("signature_type",) class ModuleT(ast.Module, scoped): pass class ExceptHandlerT(ast.ExceptHandler): - _fields = ("filter", "name", "body") # rename ast.ExceptHandler.type + _fields = ("filter", "name", "body") # rename ast.ExceptHandler.type to filter _types = ("name_type",) class SliceT(ast.Slice, commontyped): diff --git a/artiq/compiler/builtins.py b/artiq/compiler/builtins.py index c34ccf126..60c7af718 100644 --- a/artiq/compiler/builtins.py +++ b/artiq/compiler/builtins.py @@ -96,44 +96,32 @@ class TException(types.TMono): def __init__(self, name="Exception"): super().__init__(name) -class TIndexError(TException): - def __init__(self): - super().__init__("IndexError") - -class TValueError(TException): - def __init__(self): - super().__init__("ValueError") - -class TZeroDivisionError(TException): - def __init__(self): - super().__init__("ZeroDivisionError") - def fn_bool(): - return types.TConstructor("bool") + return types.TConstructor(TBool()) def fn_int(): - return types.TConstructor("int") + return types.TConstructor(TInt()) def fn_float(): - return types.TConstructor("float") + return types.TConstructor(TFloat()) def fn_str(): - return types.TConstructor("str") + return types.TConstructor(TStr()) def fn_list(): - return types.TConstructor("list") + return types.TConstructor(TList()) def fn_Exception(): - return types.TExceptionConstructor("Exception") + return types.TExceptionConstructor(TException("Exception")) def fn_IndexError(): - return types.TExceptionConstructor("IndexError") + return types.TExceptionConstructor(TException("IndexError")) def fn_ValueError(): - return types.TExceptionConstructor("ValueError") + return types.TExceptionConstructor(TException("ValueError")) def fn_ZeroDivisionError(): - return types.TExceptionConstructor("ZeroDivisionError") + return types.TExceptionConstructor(TException("ZeroDivisionError")) def fn_range(): return types.TBuiltinFunction("range") @@ -215,4 +203,4 @@ def is_collection(typ): def is_allocated(typ): return typ.fold(False, lambda accum, typ: is_list(typ) or is_str(typ) or types.is_function(typ) or - is_exception(typ)) + is_exception(typ) or types.is_constructor(typ)) diff --git a/artiq/compiler/ir.py b/artiq/compiler/ir.py index 2561d4ea4..0f3ca3131 100644 --- a/artiq/compiler/ir.py +++ b/artiq/compiler/ir.py @@ -636,9 +636,9 @@ class SetAttr(Instruction): assert isinstance(attr, (str, int)) assert isinstance(value, Value) if isinstance(attr, int): - assert value.type == obj.type.elts[attr] + assert value.type == obj.type.elts[attr].find() else: - assert value.type == obj.type.attributes[attr] + assert value.type == obj.type.attributes[attr].find() super().__init__([obj, value], builtins.TNone(), name) self.attr = attr diff --git a/artiq/compiler/transforms/artiq_ir_generator.py b/artiq/compiler/transforms/artiq_ir_generator.py index a822f8815..01fd558b2 100644 --- a/artiq/compiler/transforms/artiq_ir_generator.py +++ b/artiq/compiler/transforms/artiq_ir_generator.py @@ -73,6 +73,7 @@ class ARTIQIRGenerator(algorithm.Visitor): self.name = [module_name] if module_name != "" else [] self.current_loc = None self.current_function = None + self.current_class = None self.current_globals = set() self.current_block = None self.current_env = None @@ -156,6 +157,17 @@ class ARTIQIRGenerator(algorithm.Visitor): # 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, is_internal): if is_lambda: name = "lambda@{}:{}".format(node.loc.line(), node.loc.column()) @@ -239,9 +251,12 @@ class ARTIQIRGenerator(algorithm.Visitor): return self.append(ir.Closure(func, self.current_env)) - def visit_FunctionDefT(self, node): + def visit_FunctionDefT(self, node, in_class=None): func = self.visit_function(node, is_lambda=False, is_internal=len(self.name) > 2) - self._set_local(node.name, func) + if in_class is None: + self._set_local(node.name, func) + else: + self.append(ir.SetAttr(in_class, node.name, func)) def visit_Return(self, node): if node.value is None: @@ -668,9 +683,19 @@ class ARTIQIRGenerator(algorithm.Visitor): return self.current_env def _get_local(self, name): - return self.append(ir.GetLocal(self._env_for(name), name, name="local." + 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="local." + name)) + + return self.append(ir.GetLocal(self._env_for(name), name, + name="local." + 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): @@ -706,7 +731,7 @@ class ARTIQIRGenerator(algorithm.Visitor): head = self.current_block self.current_block = out_of_bounds_block = self.add_block() - exn = self.alloc_exn(builtins.TIndexError(), + exn = self.alloc_exn(builtins.TException("IndexError"), ir.Constant("index {0} out of bounds 0:{1}", builtins.TStr()), index, length) self.raise_exn(exn, loc=loc) @@ -790,7 +815,7 @@ class ARTIQIRGenerator(algorithm.Visitor): step = self.visit(node.slice.step) self._make_check( self.append(ir.Compare(ast.NotEq(loc=None), step, ir.Constant(0, step.type))), - lambda: self.alloc_exn(builtins.TValueError(), + lambda: self.alloc_exn(builtins.TException("ValueError"), ir.Constant("step cannot be zero", builtins.TStr())), loc=node.slice.step.loc) else: @@ -811,7 +836,7 @@ class ARTIQIRGenerator(algorithm.Visitor): name="slice.size")) self._make_check( self.append(ir.Compare(ast.LtE(loc=None), slice_size, length)), - lambda: self.alloc_exn(builtins.TValueError(), + lambda: self.alloc_exn(builtins.TException("ValueError"), ir.Constant("slice size {0} is larger than iterable length {1}", builtins.TStr()), slice_size, length), @@ -894,7 +919,7 @@ class ARTIQIRGenerator(algorithm.Visitor): self._make_check( self.append(ir.Compare(ast.Eq(loc=None), length, ir.Constant(len(node.elts), self._size_type))), - lambda: self.alloc_exn(builtins.TValueError(), + lambda: self.alloc_exn(builtins.TException("ValueError"), ir.Constant("list must be {0} elements long to decompose", builtins.TStr()), length)) @@ -1002,13 +1027,13 @@ class ARTIQIRGenerator(algorithm.Visitor): # 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.TValueError(), + 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)): self._make_check( self.append(ir.Compare(ast.NotEq(loc=None), rhs, ir.Constant(0, rhs.type))), - lambda: self.alloc_exn(builtins.TZeroDivisionError(), + lambda: self.alloc_exn(builtins.TException("ZeroDivisionError"), ir.Constant("cannot divide by zero", builtins.TStr())), loc=node.right.loc) diff --git a/artiq/compiler/transforms/asttyped_rewriter.py b/artiq/compiler/transforms/asttyped_rewriter.py index 51b84efb0..5f02431c4 100644 --- a/artiq/compiler/transforms/asttyped_rewriter.py +++ b/artiq/compiler/transforms/asttyped_rewriter.py @@ -3,7 +3,8 @@ to a typedtree (:mod:`..asttyped`). """ -from pythonparser import algorithm, diagnostic +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, @@ -16,7 +17,7 @@ class LocalExtractor(algorithm.Visitor): self.in_root = False self.in_assign = False - self.typing_env = {} + self.typing_env = OrderedDict() # which names are global have to be recorded in the current scope self.global_ = set() @@ -189,6 +190,7 @@ class ASTTypedRewriter(algorithm.Transformer): self.engine = engine self.globals = None self.env_stack = [prelude] + self.in_class = None def _try_find_name(self, name): for typing_env in reversed(self.env_stack): @@ -196,11 +198,17 @@ class ASTTypedRewriter(algorithm.Transformer): return typing_env[name] def _find_name(self, name, loc): + if self.in_class is not None: + typ = self.in_class.constructor_type.attributes.get(name) + if typ is not None: + return typ + typ = self._try_find_name(name) if typ is not None: return typ + diag = diagnostic.Diagnostic("fatal", - "name '{name}' is not bound to anything", {"name":name}, loc) + "undefined variable '{name}'", {"name":name}, loc) self.engine.process(diag) # Visitors that replace node with a typed node @@ -239,6 +247,66 @@ class ASTTypedRewriter(algorithm.Transformer): 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":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.TMono(node.name) + instance_type.attributes = OrderedDict({}) # TODO + + # 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 + + 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): return asttyped.argT(type=self._find_name(node.arg, node.loc), arg=node.arg, annotation=self.visit(node.annotation), @@ -426,7 +494,6 @@ class ASTTypedRewriter(algorithm.Transformer): visit_YieldFrom = visit_unsupported # stmt - visit_ClassDef = visit_unsupported visit_Delete = visit_unsupported visit_Import = visit_unsupported visit_ImportFrom = visit_unsupported diff --git a/artiq/compiler/transforms/inferencer.py b/artiq/compiler/transforms/inferencer.py index 0fe000189..5c906939d 100644 --- a/artiq/compiler/transforms/inferencer.py +++ b/artiq/compiler/transforms/inferencer.py @@ -467,7 +467,7 @@ class Inferencer(algorithm.Visitor): else: diagnose(valid_forms()) - self._unify(node.type, getattr(builtins, "T" + typ.name)(), + self._unify(node.type, typ.instance, node.loc, None) elif types.is_builtin(typ, "bool"): valid_forms = lambda: [ @@ -895,38 +895,47 @@ class Inferencer(algorithm.Visitor): "decorators are not supported", {}, node.at_locs[index], [decorator.loc]) self.engine.process(diag) - return - 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 + 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) + 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) - - self.function = old_function - self.in_loop = old_in_loop - self.has_return = old_has_return + # 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) + 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", diff --git a/artiq/compiler/transforms/llvm_ir_generator.py b/artiq/compiler/transforms/llvm_ir_generator.py index 4fd341763..5b2dfb929 100644 --- a/artiq/compiler/transforms/llvm_ir_generator.py +++ b/artiq/compiler/transforms/llvm_ir_generator.py @@ -437,7 +437,7 @@ class LLVMIRGenerator: size=llsize) llvalue = self.llbuilder.insert_value(llvalue, llalloc, 1, name=insn.name) return llvalue - elif builtins.is_exception(insn.type): + elif builtins.is_exception(insn.type) or types.is_constructor(insn.type): llalloc = self.llbuilder.alloca(self.llty_of_type(insn.type, bare=True)) for index, operand in enumerate(insn.operands): lloperand = self.map(operand) diff --git a/artiq/compiler/types.py b/artiq/compiler/types.py index 631a9107e..93b3b3d20 100644 --- a/artiq/compiler/types.py +++ b/artiq/compiler/types.py @@ -319,9 +319,17 @@ class TConstructor(TBuiltin): A type of a constructor of a builtin class, e.g. ``list``. Note that this is not the same as the type of an instance of the class, which is ``TMono("list", ...)``. + + :ivar instance: (:class:`Type`) + the type of the instance created by this constructor """ -class TExceptionConstructor(TBuiltin): + def __init__(self, instance): + assert isinstance(instance, TMono) + super().__init__(instance.name) + self.instance = instance + +class TExceptionConstructor(TConstructor): """ A type of a constructor of a builtin exception, e.g. ``Exception``. Note that this is not the same as the type of an instance of @@ -402,6 +410,14 @@ def is_builtin(typ, name=None): return isinstance(typ, TBuiltin) and \ typ.name == name +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: @@ -459,9 +475,11 @@ class TypePrinter(object): elif isinstance(typ, TFunction): return signature elif isinstance(typ, TBuiltinFunction): - return "" % typ.name + return "".format(typ.name) elif isinstance(typ, (TConstructor, TExceptionConstructor)): - return "" % typ.name + attrs = ", ".join(["{}: {}".format(attr, self.name(typ.attributes[attr])) + for attr in typ.attributes]) + return "".format(typ.name, attrs) elif isinstance(typ, TValue): return repr(typ.value) else: diff --git a/artiq/compiler/validators/escape.py b/artiq/compiler/validators/escape.py index df87c6afc..9fc053fe6 100644 --- a/artiq/compiler/validators/escape.py +++ b/artiq/compiler/validators/escape.py @@ -208,7 +208,7 @@ class EscapeValidator(algorithm.Visitor): loc) ] - def visit_in_region(self, node, region): + def visit_in_region(self, node, region, typing_env): try: old_youngest_region = self.youngest_region self.youngest_region = region @@ -216,8 +216,8 @@ class EscapeValidator(algorithm.Visitor): old_youngest_env = self.youngest_env self.youngest_env = {} - for name in node.typing_env: - if builtins.is_allocated(node.typing_env[name]): + for name in typing_env: + if builtins.is_allocated(typing_env[name]): self.youngest_env[name] = Region(None) # not yet known else: self.youngest_env[name] = None # lives forever @@ -230,11 +230,15 @@ class EscapeValidator(algorithm.Visitor): self.youngest_region = old_youngest_region def visit_ModuleT(self, node): - self.visit_in_region(node, None) + 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)) + self.visit_in_region(node, Region(node.loc), node.typing_env) + + 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) diff --git a/lit-test/test/inferencer/builtin_calls.py b/lit-test/test/inferencer/builtin_calls.py index 50597151f..1ba629fba 100644 --- a/lit-test/test/inferencer/builtin_calls.py +++ b/lit-test/test/inferencer/builtin_calls.py @@ -1,28 +1,28 @@ # RUN: %python -m artiq.compiler.testbench.inferencer %s >%t # RUN: OutputCheck %s --file-to-check=%t -# CHECK-L: bool:():bool +# CHECK-L: bool:():bool bool() -# CHECK-L: bool:([]:list(elt='a)):bool +# CHECK-L: bool:([]:list(elt='a)):bool bool([]) -# CHECK-L: int:():int(width='b) +# CHECK-L: int:():int(width='b) int() -# CHECK-L: int:(1.0:float):int(width='c) +# CHECK-L: int:(1.0:float):int(width='c) int(1.0) -# CHECK-L: int:(1.0:float, width=64:int(width='d)):int(width=64) +# CHECK-L: int:(1.0:float, width=64:int(width='d)):int(width=64) int(1.0, width=64) -# CHECK-L: float:():float +# CHECK-L: float:():float float() -# CHECK-L: float:(1:int(width='e)):float +# CHECK-L: float:(1:int(width='e)):float float(1) -# CHECK-L: list:():list(elt='f) +# CHECK-L: list:():list(elt='f) list() # CHECK-L: len:([]:list(elt='g)):int(width=32) diff --git a/lit-test/test/inferencer/class.py b/lit-test/test/inferencer/class.py new file mode 100644 index 000000000..4b14a2737 --- /dev/null +++ b/lit-test/test/inferencer/class.py @@ -0,0 +1,14 @@ +# RUN: %python -m artiq.compiler.testbench.inferencer %s >%t +# RUN: OutputCheck %s --file-to-check=%t + +class c: + a = 1 + def f(): + pass + +# CHECK-L: c:NoneType +c.f diff --git a/lit-test/test/inferencer/error_class.py b/lit-test/test/inferencer/error_class.py new file mode 100644 index 000000000..0cc075b05 --- /dev/null +++ b/lit-test/test/inferencer/error_class.py @@ -0,0 +1,10 @@ +# 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/lit-test/test/inferencer/error_local_unbound.py b/lit-test/test/inferencer/error_local_unbound.py index 6b4350157..7327a8548 100644 --- a/lit-test/test/inferencer/error_local_unbound.py +++ b/lit-test/test/inferencer/error_local_unbound.py @@ -1,5 +1,5 @@ # RUN: %python -m artiq.compiler.testbench.inferencer +diag %s >%t # RUN: OutputCheck %s --file-to-check=%t -# CHECK-L: ${LINE:+1}: fatal: name 'x' is not bound to anything +# CHECK-L: ${LINE:+1}: fatal: undefined variable 'x' x diff --git a/lit-test/test/inferencer/exception.py b/lit-test/test/inferencer/exception.py index bfdbbfb7c..e0e0f9645 100644 --- a/lit-test/test/inferencer/exception.py +++ b/lit-test/test/inferencer/exception.py @@ -1,7 +1,7 @@ # RUN: %python -m artiq.compiler.testbench.inferencer %s >%t # RUN: OutputCheck %s --file-to-check=%t -# CHECK-L: Exception: +# CHECK-L: Exception: Exception try: diff --git a/lit-test/test/inferencer/unify.py b/lit-test/test/inferencer/unify.py index c6f41b495..31679beb6 100644 --- a/lit-test/test/inferencer/unify.py +++ b/lit-test/test/inferencer/unify.py @@ -58,16 +58,16 @@ k = "x" # CHECK-L: k:str IndexError() -# CHECK-L: IndexError:():IndexError +# CHECK-L: IndexError:():IndexError IndexError("x") -# CHECK-L: IndexError:("x":str):IndexError +# CHECK-L: IndexError:("x":str):IndexError IndexError("x", 1) -# CHECK-L: IndexError:("x":str, 1:int(width=64)):IndexError +# CHECK-L: IndexError:("x":str, 1:int(width=64)):IndexError IndexError("x", 1, 1) -# CHECK-L: IndexError:("x":str, 1:int(width=64), 1:int(width=64)):IndexError +# CHECK-L: IndexError:("x":str, 1:int(width=64), 1:int(width=64)):IndexError IndexError("x", 1, 1, 1) -# CHECK-L: IndexError:("x":str, 1:int(width=64), 1:int(width=64), 1:int(width=64)):IndexError +# CHECK-L: IndexError:("x":str, 1:int(width=64), 1:int(width=64), 1:int(width=64)):IndexError diff --git a/lit-test/test/integration/class.py b/lit-test/test/integration/class.py new file mode 100644 index 000000000..1937ec85f --- /dev/null +++ b/lit-test/test/integration/class.py @@ -0,0 +1,12 @@ +# RUN: %python -m artiq.compiler.testbench.jit %s +# RUN: %python %s + +class c: + a = 1 + def f(): + return 2 + +# CHECK-L: a 1 +print("a", c.a) +# CHECK-L: f() 2 +print("f()", c.f()) From 94a2d5f5fa3ed9056d907b6f0649a2fe5b239b2b Mon Sep 17 00:00:00 2001 From: whitequark Date: Sat, 15 Aug 2015 11:04:12 -0400 Subject: [PATCH 221/369] Implement class attribute access through instances. --- artiq/compiler/builtins.py | 6 ++- artiq/compiler/ir.py | 31 +++++++++++++++- .../compiler/transforms/artiq_ir_generator.py | 15 +++++++- .../compiler/transforms/asttyped_rewriter.py | 4 +- artiq/compiler/transforms/inferencer.py | 20 +++++++--- .../compiler/transforms/llvm_ir_generator.py | 30 +++++++++------ artiq/compiler/types.py | 37 +++++++++++++++++-- lit-test/test/integration/instance.py | 16 ++++++++ 8 files changed, 130 insertions(+), 29 deletions(-) create mode 100644 lit-test/test/integration/instance.py diff --git a/artiq/compiler/builtins.py b/artiq/compiler/builtins.py index 60c7af718..16e24381c 100644 --- a/artiq/compiler/builtins.py +++ b/artiq/compiler/builtins.py @@ -202,5 +202,7 @@ def is_collection(typ): def is_allocated(typ): return typ.fold(False, lambda accum, typ: - is_list(typ) or is_str(typ) or types.is_function(typ) or - is_exception(typ) or types.is_constructor(typ)) + accum or not (is_none(typ) or is_bool(typ) or is_int(typ) or + is_float(typ) or is_range(typ) or + types.is_c_function(typ) or types.is_rpc_function(typ) or + types.is_value(typ))) diff --git a/artiq/compiler/ir.py b/artiq/compiler/ir.py index 0f3ca3131..90d5ef1a1 100644 --- a/artiq/compiler/ir.py +++ b/artiq/compiler/ir.py @@ -554,7 +554,7 @@ class GetLocal(Instruction): self.var_name = var_name def opcode(self): - return "getlocal({})".format(self.var_name) + return "getlocal({})".format(repr(self.var_name)) def environment(self): return self.operands[0] @@ -582,7 +582,7 @@ class SetLocal(Instruction): self.var_name = var_name def opcode(self): - return "setlocal({})".format(self.var_name) + return "setlocal({})".format(repr(self.var_name)) def environment(self): return self.operands[0] @@ -590,6 +590,33 @@ class SetLocal(Instruction): def value(self): return self.operands[1] +class GetConstructor(Instruction): + """ + An intruction that loads a local variable with the given type + 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 + :param var_type: (:class:`types.Type`) local variable type + """ + def __init__(self, env, var_name, var_type, name=""): + assert isinstance(env, Value) + assert isinstance(env.type, TEnvironment) + assert isinstance(var_name, str) + assert isinstance(var_type, types.Type) + super().__init__([env], var_type, name) + self.var_name = var_name + + def opcode(self): + return "getconstructor({})".format(repr(self.var_name)) + + def environment(self): + return self.operands[0] + class GetAttr(Instruction): """ An intruction that loads an attribute from an object, diff --git a/artiq/compiler/transforms/artiq_ir_generator.py b/artiq/compiler/transforms/artiq_ir_generator.py index 01fd558b2..c61449c35 100644 --- a/artiq/compiler/transforms/artiq_ir_generator.py +++ b/artiq/compiler/transforms/artiq_ir_generator.py @@ -711,6 +711,14 @@ class ARTIQIRGenerator(algorithm.Visitor): finally: self.current_assign = old_assign + if node.attr not in node.type.find().attributes: + # A class attribute. Get the constructor (class object) and + # extract the attribute from it. + constructor = obj.type.constructor + obj = self.append(ir.GetConstructor(self._env_for(constructor.name), + constructor.name, constructor, + name="constructor." + constructor.name)) + if self.current_assign is None: return self.append(ir.GetAttr(obj, node.attr, name="{}.{}".format(_readable_name(obj), node.attr))) @@ -1398,10 +1406,13 @@ class ARTIQIRGenerator(algorithm.Visitor): assert False def visit_CallT(self, node): - if types.is_builtin(node.func.type): + typ = node.func.type.find() + + if types.is_constructor(typ) and not types.is_exn_constructor(typ): + return self.append(ir.Alloc([], typ.instance)) + elif types.is_builtin(typ): return self.visit_builtin_call(node) else: - typ = node.func.type.find() func = self.visit(node.func) args = [None] * (len(typ.args) + len(typ.optargs)) diff --git a/artiq/compiler/transforms/asttyped_rewriter.py b/artiq/compiler/transforms/asttyped_rewriter.py index 5f02431c4..15f74f8ca 100644 --- a/artiq/compiler/transforms/asttyped_rewriter.py +++ b/artiq/compiler/transforms/asttyped_rewriter.py @@ -277,14 +277,14 @@ class ASTTypedRewriter(algorithm.Transformer): # 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.TMono(node.name) - instance_type.attributes = OrderedDict({}) # TODO + instance_type = types.TInstance(node.name) # 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 diff --git a/artiq/compiler/transforms/inferencer.py b/artiq/compiler/transforms/inferencer.py index 5c906939d..2400723d0 100644 --- a/artiq/compiler/transforms/inferencer.py +++ b/artiq/compiler/transforms/inferencer.py @@ -91,6 +91,11 @@ class Inferencer(algorithm.Visitor): # assumes no free type variables in .attributes self._unify(node.type, object_type.attributes[node.attr], node.loc, None) + elif types.is_instance(object_type) and \ + node.attr in object_type.constructor.attributes: + # assumes no free type variables in .attributes + self._unify(node.type, object_type.constructor.attributes[node.attr], + node.loc, None) else: diag = diagnostic.Diagnostic("error", "type {type} does not have an attribute '{attr}'", @@ -680,19 +685,24 @@ class Inferencer(algorithm.Visitor): self.engine.process(diag) return - if types.is_var(node.func.type): + typ = node.func.type.find() + + if types.is_var(typ): return # not enough info yet - elif types.is_builtin(node.func.type): + elif types.is_constructor(typ) and not types.is_exn_constructor(typ): + self._unify(node.type, typ.find().instance, + node.loc, None) + return + elif types.is_builtin(typ): return self.visit_builtin_call(node) - elif not types.is_function(node.func.type): + elif not types.is_function(typ): diag = diagnostic.Diagnostic("error", "cannot call this expression of type {type}", - {"type": types.TypePrinter().name(node.func.type)}, + {"type": types.TypePrinter().name(typ)}, node.func.loc, []) self.engine.process(diag) return - typ = node.func.type.find() passed_args = dict() if len(node.args) > typ.arity(): diff --git a/artiq/compiler/transforms/llvm_ir_generator.py b/artiq/compiler/transforms/llvm_ir_generator.py index 5b2dfb929..e068dcc05 100644 --- a/artiq/compiler/transforms/llvm_ir_generator.py +++ b/artiq/compiler/transforms/llvm_ir_generator.py @@ -224,7 +224,9 @@ class LLVMIRGenerator: return llty.as_pointer() else: # Catch-all for exceptions and custom classes if builtins.is_exception(typ): - name = 'Exception' # they all share layout + name = "class.Exception" # they all share layout + elif types.is_constructor(typ): + name = "class.{}".format(typ.name) else: name = typ.name @@ -437,24 +439,23 @@ class LLVMIRGenerator: size=llsize) llvalue = self.llbuilder.insert_value(llvalue, llalloc, 1, name=insn.name) return llvalue - elif builtins.is_exception(insn.type) or types.is_constructor(insn.type): + elif not builtins.is_allocated(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 + 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)]) self.llbuilder.store(lloperand, llfieldptr) return llalloc - elif builtins.is_allocated(insn.type): - assert False - else: # immutable - 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 - def llptr_to_var(self, llenv, env_ty, var_name): - if var_name in env_ty.params: + def llptr_to_var(self, llenv, env_ty, var_name, var_type=None): + if var_name in env_ty.params and (var_type is None or + env_ty.params[var_name] == var_type): var_index = list(env_ty.params.keys()).index(var_name) return self.llbuilder.gep(llenv, [self.llindex(0), self.llindex(var_index)]) else: @@ -468,6 +469,11 @@ class LLVMIRGenerator: llptr = self.llptr_to_var(self.map(env), env.type, insn.var_name) return self.llbuilder.load(llptr) + def process_GetConstructor(self, insn): + env = insn.environment() + llptr = self.llptr_to_var(self.map(env), env.type, insn.var_name, insn.type) + return self.llbuilder.load(llptr) + def process_SetLocal(self, insn): env = insn.environment() llptr = self.llptr_to_var(self.map(env), env.type, insn.var_name) diff --git a/artiq/compiler/types.py b/artiq/compiler/types.py index 93b3b3d20..152b71cc8 100644 --- a/artiq/compiler/types.py +++ b/artiq/compiler/types.py @@ -300,7 +300,7 @@ class TBuiltin(Type): return fn(accum, self) def __repr__(self): - return "py2llvm.types.TBuiltin(%s)" % repr(self.name) + return "py2llvm.types.{}({})".format(type(self).__name__, repr(self.name)) def __eq__(self, other): return isinstance(other, TBuiltin) and \ @@ -316,9 +316,9 @@ class TBuiltinFunction(TBuiltin): class TConstructor(TBuiltin): """ - A type of a constructor of a builtin class, e.g. ``list``. + 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", ...)``. + the class, which is ``TMono("list", ...)`` (or a descendant). :ivar instance: (:class:`Type`) the type of the instance created by this constructor @@ -331,11 +331,29 @@ class TConstructor(TBuiltin): class TExceptionConstructor(TConstructor): """ - A type of a constructor of a builtin exception, e.g. ``Exception``. + 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=OrderedDict()): + super().__init__(name) + self.attributes = attributes + + def __repr__(self): + return "py2llvm.types.TInstance({}, {]})".format( + repr(self.name), repr(self.attributes)) + + class TValue(Type): """ A type-level value (such as the integer denoting width of @@ -426,6 +444,17 @@ def is_exn_constructor(typ, name=None): 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_value(typ): + return isinstance(typ.find(), TValue) + def get_value(typ): typ = typ.find() if isinstance(typ, TVar): diff --git a/lit-test/test/integration/instance.py b/lit-test/test/integration/instance.py new file mode 100644 index 000000000..7d55f3c28 --- /dev/null +++ b/lit-test/test/integration/instance.py @@ -0,0 +1,16 @@ +# RUN: %python -m artiq.compiler.testbench.jit %s +# RUN: %python %s + +class c: + a = 1 + +i = c() + +# CHECK-L: a 1 +print("a", i.a) + +def f(): + c = None + # CHECK-L: shadow a 1 + print("shadow a", i.a) +f() From 1040a409c3c6ab23909cc767539ddcc2de974f9f Mon Sep 17 00:00:00 2001 From: whitequark Date: Tue, 18 Aug 2015 15:05:43 -0700 Subject: [PATCH 222/369] lit-test: fix tests incorrectly assuming an OutputCheck step. --- lit-test/test/integration/class.py | 6 ++---- lit-test/test/integration/finally.py | 6 ++++-- lit-test/test/integration/instance.py | 6 ++---- 3 files changed, 8 insertions(+), 10 deletions(-) diff --git a/lit-test/test/integration/class.py b/lit-test/test/integration/class.py index 1937ec85f..8528636b2 100644 --- a/lit-test/test/integration/class.py +++ b/lit-test/test/integration/class.py @@ -6,7 +6,5 @@ class c: def f(): return 2 -# CHECK-L: a 1 -print("a", c.a) -# CHECK-L: f() 2 -print("f()", c.f()) +assert c.a == 1 +assert c.f() == 2 diff --git a/lit-test/test/integration/finally.py b/lit-test/test/integration/finally.py index fab1fe93d..d418fbbe5 100644 --- a/lit-test/test/integration/finally.py +++ b/lit-test/test/integration/finally.py @@ -1,5 +1,7 @@ -# RUN: %python -m artiq.compiler.testbench.jit %s -# RUN: %python %s +# 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(): diff --git a/lit-test/test/integration/instance.py b/lit-test/test/integration/instance.py index 7d55f3c28..bf255d88f 100644 --- a/lit-test/test/integration/instance.py +++ b/lit-test/test/integration/instance.py @@ -6,11 +6,9 @@ class c: i = c() -# CHECK-L: a 1 -print("a", i.a) +assert i.a == 1 def f(): c = None - # CHECK-L: shadow a 1 - print("shadow a", i.a) + assert i.a == 1 f() From 6c8de9b6d4131ac87b81f648f001202046744d90 Mon Sep 17 00:00:00 2001 From: whitequark Date: Tue, 18 Aug 2015 22:39:22 -0700 Subject: [PATCH 223/369] Implement methods. --- artiq/compiler/builtins.py | 1 + .../compiler/transforms/artiq_ir_generator.py | 58 +++++++++---- artiq/compiler/transforms/inferencer.py | 85 ++++++++++++++----- .../compiler/transforms/llvm_ir_generator.py | 4 + artiq/compiler/types.py | 24 +++++- lit-test/test/inferencer/class.py | 7 +- lit-test/test/inferencer/error_method.py | 16 ++++ lit-test/test/integration/class.py | 3 + 8 files changed, 158 insertions(+), 40 deletions(-) create mode 100644 lit-test/test/inferencer/error_method.py diff --git a/artiq/compiler/builtins.py b/artiq/compiler/builtins.py index 16e24381c..efac80e9a 100644 --- a/artiq/compiler/builtins.py +++ b/artiq/compiler/builtins.py @@ -205,4 +205,5 @@ def is_allocated(typ): accum or not (is_none(typ) or is_bool(typ) or is_int(typ) or is_float(typ) or is_range(typ) or types.is_c_function(typ) or types.is_rpc_function(typ) or + types.is_method(typ) or types.is_value(typ))) diff --git a/artiq/compiler/transforms/artiq_ir_generator.py b/artiq/compiler/transforms/artiq_ir_generator.py index c61449c35..30c821082 100644 --- a/artiq/compiler/transforms/artiq_ir_generator.py +++ b/artiq/compiler/transforms/artiq_ir_generator.py @@ -711,13 +711,22 @@ class ARTIQIRGenerator(algorithm.Visitor): finally: self.current_assign = old_assign - if node.attr not in node.type.find().attributes: + if node.attr not in obj.type.find().attributes: # A class attribute. Get the constructor (class object) and # extract the attribute from it. - constructor = obj.type.constructor - obj = self.append(ir.GetConstructor(self._env_for(constructor.name), - constructor.name, constructor, - name="constructor." + constructor.name)) + print(node) + print(obj) + constr_type = obj.type.constructor + constr = self.append(ir.GetConstructor(self._env_for(constr_type.name), + constr_type.name, constr_type, + name="constructor." + constr_type.name)) + + if types.is_function(constr.type.attributes[node.attr]): + # A method. Construct a method object instead. + func = self.append(ir.GetAttr(constr, node.attr)) + return self.append(ir.Alloc([func, obj], node.type)) + else: + obj = constr if self.current_assign is None: return self.append(ir.GetAttr(obj, node.attr, @@ -1413,36 +1422,49 @@ class ARTIQIRGenerator(algorithm.Visitor): elif types.is_builtin(typ): return self.visit_builtin_call(node) else: - func = self.visit(node.func) - args = [None] * (len(typ.args) + len(typ.optargs)) + if types.is_function(typ): + func = self.visit(node.func) + self_arg = None + fn_typ = typ + elif types.is_method(typ): + method = self.visit(node.func) + func = self.append(ir.GetAttr(method, "__func__")) + self_arg = self.append(ir.GetAttr(method, "__self__")) + fn_typ = types.get_method_function(typ) + + args = [None] * (len(fn_typ.args) + len(fn_typ.optargs)) for index, arg_node in enumerate(node.args): arg = self.visit(arg_node) - if index < len(typ.args): + if index < len(fn_typ.args): args[index] = arg else: args[index] = self.append(ir.Alloc([arg], ir.TOption(arg.type))) for keyword in node.keywords: arg = self.visit(keyword.value) - if keyword.arg in typ.args: - for index, arg_name in enumerate(typ.args): + if keyword.arg in fn_typ.args: + for index, arg_name in enumerate(fn_typ.args): if keyword.arg == arg_name: assert args[index] is None args[index] = arg break - elif keyword.arg in typ.optargs: - for index, optarg_name in enumerate(typ.optargs): + elif keyword.arg in fn_typ.optargs: + for index, optarg_name in enumerate(fn_typ.optargs): if keyword.arg == optarg_name: - assert args[len(typ.args) + index] is None - args[len(typ.args) + index] = \ + 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(typ.optargs): - if args[len(typ.args) + index] is None: - args[len(typ.args) + index] = \ - self.append(ir.Alloc([], ir.TOption(typ.optargs[optarg_name]))) + 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 diff --git a/artiq/compiler/transforms/inferencer.py b/artiq/compiler/transforms/inferencer.py index 2400723d0..8711aeedf 100644 --- a/artiq/compiler/transforms/inferencer.py +++ b/artiq/compiler/transforms/inferencer.py @@ -22,7 +22,7 @@ class Inferencer(algorithm.Visitor): self.in_loop = False self.has_return = False - def _unify(self, typea, typeb, loca, locb, makenotes=None): + def _unify(self, typea, typeb, loca, locb, makenotes=None, when=""): try: typea.unify(typeb) except types.UnificationError as e: @@ -45,16 +45,19 @@ class Inferencer(algorithm.Visitor): locb)) highlights = [locb] if locb else [] - if e.typea.find() == typea.find() and e.typeb.find() == typeb.find(): + 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}", - {"typea": printer.name(typea), "typeb": printer.name(typeb)}, + "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}: {fraga} is incompatible with {fragb}", + "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)}, + "fraga": printer.name(e.typea), "fragb": printer.name(e.typeb), + "when": when}, loca, highlights, notes) self.engine.process(diag) @@ -88,13 +91,43 @@ class Inferencer(algorithm.Visitor): object_type = node.value.type.find() if not types.is_var(object_type): if node.attr in object_type.attributes: - # assumes no free type variables in .attributes + # Assumes no free type variables in .attributes. self._unify(node.type, object_type.attributes[node.attr], node.loc, None) elif types.is_instance(object_type) and \ node.attr in object_type.constructor.attributes: - # assumes no free type variables in .attributes - self._unify(node.type, object_type.constructor.attributes[node.attr], + # Assumes no free type variables in .attributes. + attr_type = object_type.constructor.attributes[node.attr].find() + if types.is_function(attr_type): + # Convert to a method. + if len(attr_type.args) < 1: + diag = diagnostic.Diagnostic("error", + "function '{attr}{type}' of class '{class}' cannot accept a self argument", + {"attr": node.attr, "type": types.TypePrinter().name(attr_type), + "class": object_type.name}, + node.loc) + self.engine.process(diag) + return + else: + def makenotes(printer, typea, typeb, loca, locb): + return [ + diagnostic.Diagnostic("note", + "expression of type {typea}", + {"typea": printer.name(typea)}, + loca), + diagnostic.Diagnostic("note", + "reference to a class function of type {typeb}", + {"typeb": printer.name(attr_type)}, + locb) + ] + + self._unify(object_type, list(attr_type.args.values())[0], + node.value.loc, node.loc, + makenotes=makenotes, + when=" while inferring the type for self argument") + + attr_type = types.TMethod(object_type, attr_type) + self._unify(node.type, attr_type, node.loc, None) else: diag = diagnostic.Diagnostic("error", @@ -695,7 +728,7 @@ class Inferencer(algorithm.Visitor): return elif types.is_builtin(typ): return self.visit_builtin_call(node) - elif not types.is_function(typ): + 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)}, @@ -703,22 +736,34 @@ class Inferencer(algorithm.Visitor): 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 = types.get_method_function(typ) + typ_arity = typ.arity() - 1 + typ_args = OrderedDict(list(typ.args.items())[1:]) + typ_optargs = typ.optargs + typ_ret = typ.ret + passed_args = dict() - if len(node.args) > typ.arity(): + if len(node.args) > typ_arity: note = diagnostic.Diagnostic("note", "extraneous argument(s)", {}, - node.args[typ.arity()].loc.join(node.args[-1].loc)) + 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()}, + "num": typ_arity}, node.func.loc, [], [note]) self.engine.process(diag) return for actualarg, (formalname, formaltyp) in \ - zip(node.args, list(typ.args.items()) + list(typ.optargs.items())): + zip(node.args, list(typ_args.items()) + list(typ_optargs.items())): self._unify(actualarg.type, formaltyp, actualarg.loc, None) passed_args[formalname] = actualarg.loc @@ -732,15 +777,15 @@ class Inferencer(algorithm.Visitor): self.engine.process(diag) return - if keyword.arg in typ.args: - self._unify(keyword.value.type, typ.args[keyword.arg], + 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], + elif keyword.arg in typ_optargs: + self._unify(keyword.value.type, typ_optargs[keyword.arg], keyword.value.loc, None) passed_args[keyword.arg] = keyword.arg_loc - for formalname in typ.args: + for formalname in typ_args: if formalname not in passed_args: note = diagnostic.Diagnostic("note", "the called function is of type {type}", @@ -753,7 +798,7 @@ class Inferencer(algorithm.Visitor): self.engine.process(diag) return - self._unify(node.type, typ.ret, + self._unify(node.type, typ_ret, node.loc, None) def visit_LambdaT(self, node): diff --git a/artiq/compiler/transforms/llvm_ir_generator.py b/artiq/compiler/transforms/llvm_ir_generator.py index e068dcc05..981d76305 100644 --- a/artiq/compiler/transforms/llvm_ir_generator.py +++ b/artiq/compiler/transforms/llvm_ir_generator.py @@ -192,6 +192,10 @@ class LLVMIRGenerator: return llty else: return ll.LiteralStructType([envarg, llty.as_pointer()]) + elif types.is_method(typ): + llfuncty = self.llty_of_type(types.get_method_function(typ)) + llselfty = self.llty_of_type(types.get_method_self(typ)) + return ll.LiteralStructType([llfuncty, llselfty]) elif builtins.is_none(typ): if for_return: return llvoid diff --git a/artiq/compiler/types.py b/artiq/compiler/types.py index 152b71cc8..0226d8fe9 100644 --- a/artiq/compiler/types.py +++ b/artiq/compiler/types.py @@ -350,9 +350,20 @@ class TInstance(TMono): self.attributes = attributes def __repr__(self): - return "py2llvm.types.TInstance({}, {]})".format( + return "py2llvm.types.TInstance({}, {})".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): """ @@ -452,6 +463,17 @@ def is_instance(typ, name=None): else: return isinstance(typ, TInstance) +def is_method(typ): + return isinstance(typ.find(), TMethod) + +def get_method_self(typ): + if is_method(typ): + return typ.find().params["self"] + +def get_method_function(typ): + if is_method(typ): + return typ.find().params["fn"] + def is_value(typ): return isinstance(typ.find(), TValue) diff --git a/lit-test/test/inferencer/class.py b/lit-test/test/inferencer/class.py index 4b14a2737..2efdf1561 100644 --- a/lit-test/test/inferencer/class.py +++ b/lit-test/test/inferencer/class.py @@ -5,10 +5,15 @@ class c: a = 1 def f(): pass + def m(self): + pass -# CHECK-L: c:NoneType, m: (self:c)->NoneType}> c # CHECK-L: .a:int(width='a) c.a # CHECK-L: .f:()->NoneType c.f + +# CHECK-L: .m:method(self=c, fn=(self:c)->NoneType) +c().m() diff --git a/lit-test/test/inferencer/error_method.py b/lit-test/test/inferencer/error_method.py new file mode 100644 index 000000000..bf9b1fbe8 --- /dev/null +++ b/lit-test/test/inferencer/error_method.py @@ -0,0 +1,16 @@ +# 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' of class 'c' cannot accept a self argument +c().f() + +c.g(1) +# CHECK-L: ${LINE:+1}: error: cannot unify c with int(width='a) while inferring the type for self argument +c().g() diff --git a/lit-test/test/integration/class.py b/lit-test/test/integration/class.py index 8528636b2..3d9048a1b 100644 --- a/lit-test/test/integration/class.py +++ b/lit-test/test/integration/class.py @@ -5,6 +5,9 @@ class c: a = 1 def f(): return 2 + def g(self): + return self.a + 5 assert c.a == 1 assert c.f() == 2 +assert c().g() == 6 From 51c591f01aa439907fd797c584b3d7c21477ca65 Mon Sep 17 00:00:00 2001 From: whitequark Date: Tue, 18 Aug 2015 22:44:09 -0700 Subject: [PATCH 224/369] Unbreak tests. --- artiq/compiler/builtins.py | 11 +++++------ artiq/compiler/transforms/artiq_ir_generator.py | 6 +++--- artiq/compiler/transforms/inferencer.py | 10 ++++++---- artiq/compiler/transforms/llvm_ir_generator.py | 2 +- artiq/compiler/types.py | 4 ++-- artiq/compiler/validators/escape.py | 11 +++++++---- lit-test/test/inferencer/class.py | 2 +- 7 files changed, 25 insertions(+), 21 deletions(-) diff --git a/artiq/compiler/builtins.py b/artiq/compiler/builtins.py index efac80e9a..1a5cc3670 100644 --- a/artiq/compiler/builtins.py +++ b/artiq/compiler/builtins.py @@ -201,9 +201,8 @@ def is_collection(typ): types.is_mono(typ, "list") def is_allocated(typ): - return typ.fold(False, lambda accum, typ: - accum or not (is_none(typ) or is_bool(typ) or is_int(typ) or - is_float(typ) or is_range(typ) or - types.is_c_function(typ) or types.is_rpc_function(typ) or - types.is_method(typ) or - types.is_value(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_c_function(typ) or types.is_rpc_function(typ) or + types.is_method(typ) or types.is_tuple(typ) or + types.is_value(typ)) diff --git a/artiq/compiler/transforms/artiq_ir_generator.py b/artiq/compiler/transforms/artiq_ir_generator.py index 30c821082..5a101cdec 100644 --- a/artiq/compiler/transforms/artiq_ir_generator.py +++ b/artiq/compiler/transforms/artiq_ir_generator.py @@ -1411,15 +1411,15 @@ class ARTIQIRGenerator(algorithm.Visitor): return ir.Constant(None, builtins.TNone()) 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: assert False def visit_CallT(self, node): typ = node.func.type.find() - if types.is_constructor(typ) and not types.is_exn_constructor(typ): - return self.append(ir.Alloc([], typ.instance)) - elif types.is_builtin(typ): + if types.is_builtin(typ): return self.visit_builtin_call(node) else: if types.is_function(typ): diff --git a/artiq/compiler/transforms/inferencer.py b/artiq/compiler/transforms/inferencer.py index 8711aeedf..93a5c2c7e 100644 --- a/artiq/compiler/transforms/inferencer.py +++ b/artiq/compiler/transforms/inferencer.py @@ -705,6 +705,12 @@ class Inferencer(algorithm.Visitor): pass else: diagnose(valid_forms()) + elif types.is_constructor(typ): + # An user-defined class. + self._unify(node.type, typ.find().instance, + node.loc, None) + else: + assert False def visit_CallT(self, node): self.generic_visit(node) @@ -722,10 +728,6 @@ class Inferencer(algorithm.Visitor): if types.is_var(typ): return # not enough info yet - elif types.is_constructor(typ) and not types.is_exn_constructor(typ): - self._unify(node.type, typ.find().instance, - node.loc, None) - return elif types.is_builtin(typ): return self.visit_builtin_call(node) elif not (types.is_function(typ) or types.is_method(typ)): diff --git a/artiq/compiler/transforms/llvm_ir_generator.py b/artiq/compiler/transforms/llvm_ir_generator.py index 981d76305..e32e49d8f 100644 --- a/artiq/compiler/transforms/llvm_ir_generator.py +++ b/artiq/compiler/transforms/llvm_ir_generator.py @@ -391,7 +391,7 @@ class LLVMIRGenerator: assert llinsn is not None self.llmap[insn] = llinsn - if insn.loc is not None: + if insn.loc is not None and not isinstance(llinsn, ll.Constant): diloc = self.debug_info_emitter.emit_loc(insn.loc, disubprogram) llinsn.set_metadata('dbg', diloc) diff --git a/artiq/compiler/types.py b/artiq/compiler/types.py index 0226d8fe9..34a86dd11 100644 --- a/artiq/compiler/types.py +++ b/artiq/compiler/types.py @@ -99,8 +99,8 @@ class TMono(Type): attributes = OrderedDict() def __init__(self, name, params={}): - assert isinstance(params, dict) - self.name, self.params = name, params + assert isinstance(params, (dict, OrderedDict)) + self.name, self.params = name, OrderedDict(sorted(params.items())) def find(self): return self diff --git a/artiq/compiler/validators/escape.py b/artiq/compiler/validators/escape.py index 9fc053fe6..386cd388b 100644 --- a/artiq/compiler/validators/escape.py +++ b/artiq/compiler/validators/escape.py @@ -7,6 +7,9 @@ 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 Region: """ A last-in-first-out allocation region. Tied to lexical scoping @@ -78,7 +81,7 @@ class RegionOf(algorithm.Visitor): # Value lives as long as the current scope, if it's mutable, # or else forever def visit_sometimes_allocating(self, node): - if builtins.is_allocated(node.type): + if has_region(node.type): return self.youngest_region else: return None @@ -89,7 +92,7 @@ class RegionOf(algorithm.Visitor): # Value lives as long as the object/container, if it's mutable, # or else forever def visit_accessor(self, node): - if builtins.is_allocated(node.type): + if has_region(node.type): return self.visit(node.value) else: return None @@ -131,7 +134,7 @@ class RegionOf(algorithm.Visitor): # Value lives forever def visit_immutable(self, node): - assert not builtins.is_allocated(node.type) + assert not has_region(node.type) return None visit_NameConstantT = visit_immutable @@ -217,7 +220,7 @@ class EscapeValidator(algorithm.Visitor): self.youngest_env = {} for name in typing_env: - if builtins.is_allocated(typing_env[name]): + if has_region(typing_env[name]): self.youngest_env[name] = Region(None) # not yet known else: self.youngest_env[name] = None # lives forever diff --git a/lit-test/test/inferencer/class.py b/lit-test/test/inferencer/class.py index 2efdf1561..455f3e810 100644 --- a/lit-test/test/inferencer/class.py +++ b/lit-test/test/inferencer/class.py @@ -15,5 +15,5 @@ c.a # CHECK-L: .f:()->NoneType c.f -# CHECK-L: .m:method(self=c, fn=(self:c)->NoneType) +# CHECK-L: .m:method(fn=(self:c)->NoneType, self=c) c().m() From 53b4d87647c0c0f0f32fe035487322506e0caa3e Mon Sep 17 00:00:00 2001 From: whitequark Date: Wed, 19 Aug 2015 12:18:20 -0700 Subject: [PATCH 225/369] LLVMIRGenerator: attach debug metadata to all emitted LLVM instructions. --- artiq/compiler/transforms/llvm_ir_generator.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/artiq/compiler/transforms/llvm_ir_generator.py b/artiq/compiler/transforms/llvm_ir_generator.py index e32e49d8f..fec9a16cd 100644 --- a/artiq/compiler/transforms/llvm_ir_generator.py +++ b/artiq/compiler/transforms/llvm_ir_generator.py @@ -387,14 +387,14 @@ class LLVMIRGenerator: for block in func.basic_blocks: self.llbuilder.position_at_end(self.llmap[block]) for insn in block.instructions: + if insn.loc is not None: + self.llbuilder.debug_metadata = \ + self.debug_info_emitter.emit_loc(insn.loc, disubprogram) + llinsn = getattr(self, "process_" + type(insn).__name__)(insn) assert llinsn is not None self.llmap[insn] = llinsn - if insn.loc is not None and not isinstance(llinsn, ll.Constant): - diloc = self.debug_info_emitter.emit_loc(insn.loc, disubprogram) - llinsn.set_metadata('dbg', diloc) - # 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 From afc3f36104efc4d38347c951732008a9366929bf Mon Sep 17 00:00:00 2001 From: whitequark Date: Wed, 19 Aug 2015 12:37:22 -0700 Subject: [PATCH 226/369] ARTIQIRGenerator: fix polymorphic print on closures. --- artiq/compiler/builtins.py | 1 + artiq/compiler/transforms/artiq_ir_generator.py | 5 ++--- artiq/compiler/transforms/llvm_ir_generator.py | 2 ++ artiq/compiler/types.py | 16 +++++++++++++++- 4 files changed, 20 insertions(+), 4 deletions(-) diff --git a/artiq/compiler/builtins.py b/artiq/compiler/builtins.py index 1a5cc3670..2e26b8f79 100644 --- a/artiq/compiler/builtins.py +++ b/artiq/compiler/builtins.py @@ -203,6 +203,7 @@ def is_collection(typ): 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_c_function(typ) or types.is_rpc_function(typ) or types.is_method(typ) or types.is_tuple(typ) or types.is_value(typ)) diff --git a/artiq/compiler/transforms/artiq_ir_generator.py b/artiq/compiler/transforms/artiq_ir_generator.py index 5a101cdec..b6cd2863e 100644 --- a/artiq/compiler/transforms/artiq_ir_generator.py +++ b/artiq/compiler/transforms/artiq_ir_generator.py @@ -1575,9 +1575,8 @@ class ARTIQIRGenerator(algorithm.Visitor): format_string += ")" elif types.is_function(value.type): format_string += "" - # We're relying on the internal layout of the closure here. - args.append(self.append(ir.GetAttr(value, 0))) - args.append(self.append(ir.GetAttr(value, 1))) + 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): diff --git a/artiq/compiler/transforms/llvm_ir_generator.py b/artiq/compiler/transforms/llvm_ir_generator.py index fec9a16cd..660cf6750 100644 --- a/artiq/compiler/transforms/llvm_ir_generator.py +++ b/artiq/compiler/transforms/llvm_ir_generator.py @@ -180,6 +180,8 @@ class LLVMIRGenerator: return llvoid else: return ll.LiteralStructType([]) + elif types._is_pointer(typ): + return llptr elif types.is_function(typ): envarg = llptr llty = ll.FunctionType(args=[envarg] + diff --git a/artiq/compiler/types.py b/artiq/compiler/types.py index 34a86dd11..e1a3a406b 100644 --- a/artiq/compiler/types.py +++ b/artiq/compiler/types.py @@ -176,6 +176,10 @@ class TTuple(Type): def __ne__(self, other): return not (self == other) +class _TPointer(TMono): + def __init__(self): + super().__init__("pointer") + class TFunction(Type): """ A function type. @@ -188,7 +192,10 @@ class TFunction(Type): return type """ - attributes = OrderedDict() + attributes = OrderedDict([ + ('__code__', _TPointer()), + ('__closure__', _TPointer()), + ]) def __init__(self, args, optargs, ret): assert isinstance(args, OrderedDict) @@ -245,6 +252,8 @@ class TRPCFunction(TFunction): :ivar service: (int) RPC service number """ + attributes = OrderedDict() + def __init__(self, args, optargs, ret, service): super().__init__(args, optargs, ret) self.service = service @@ -265,6 +274,8 @@ class TCFunction(TFunction): :ivar name: (str) C function name """ + attributes = OrderedDict() + def __init__(self, args, ret, name): super().__init__(args, OrderedDict(), ret) self.name = name @@ -422,6 +433,9 @@ def is_tuple(typ, elts=None): else: return isinstance(typ, TTuple) +def _is_pointer(typ): + return isinstance(typ.find(), _TPointer) + def is_function(typ): return isinstance(typ.find(), TFunction) From 673512f356e5095f2caac13f81381ec6618f39e2 Mon Sep 17 00:00:00 2001 From: whitequark Date: Wed, 19 Aug 2015 12:37:31 -0700 Subject: [PATCH 227/369] coredevice.core: fix imports. --- artiq/coredevice/core.py | 1 + 1 file changed, 1 insertion(+) diff --git a/artiq/coredevice/core.py b/artiq/coredevice/core.py index 9408693b7..6246f6301 100644 --- a/artiq/coredevice/core.py +++ b/artiq/coredevice/core.py @@ -4,6 +4,7 @@ from pythonparser import diagnostic from artiq.language.core import * from artiq.language.types import * +from artiq.language.units import * from artiq.compiler import Stitcher, Module from artiq.compiler.targets import OR1KTarget From 27a697920ac55fe6c8c36c91de6abe4912d12d86 Mon Sep 17 00:00:00 2001 From: whitequark Date: Wed, 19 Aug 2015 15:06:03 -0700 Subject: [PATCH 228/369] LLVMIRGenerator: use sret when returning large structures. --- .../compiler/transforms/llvm_ir_generator.py | 58 +++++++++++++++++-- 1 file changed, 53 insertions(+), 5 deletions(-) diff --git a/artiq/compiler/transforms/llvm_ir_generator.py b/artiq/compiler/transforms/llvm_ir_generator.py index 660cf6750..2956020c8 100644 --- a/artiq/compiler/transforms/llvm_ir_generator.py +++ b/artiq/compiler/transforms/llvm_ir_generator.py @@ -171,6 +171,21 @@ class LLVMIRGenerator: self.phis = [] self.debug_info_emitter = DebugInfoEmitter(self.llmodule) + def needs_sret(self, lltyp, may_be_large=True): + if isinstance(lltyp, ll.VoidType): + return False + elif isinstance(lltyp, ll.IntType) and lltyp.width <= 32: + 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: + return True + def llty_of_type(self, typ, bare=False, for_return=False): typ = typ.find() if types.is_tuple(typ): @@ -183,13 +198,28 @@ class LLVMIRGenerator: elif types._is_pointer(typ): return llptr 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=[envarg] + + 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=self.llty_of_type(typ.ret, for_return=True)) + return_type=llretty) + + # TODO: actually mark the first argument as sret (also noalias nocapture). + # llvmlite currently does not have support for this; + # https://github.com/numba/llvmlite/issues/91. + if sretarg: + llty.__has_sret = True + else: + llty.__has_sret = False + if bare: return llty else: @@ -896,8 +926,22 @@ class LLVMIRGenerator: name=insn.name) else: llfun, llargs = self._prepare_closure_call(insn) - return self.llbuilder.call(llfun, llargs, - name=insn.name) + + if llfun.type.pointee.__has_sret: + llstackptr = self.llbuilder.call(self.llbuiltin("llvm.stacksave"), []) + + llresultslot = self.llbuilder.alloca(llfun.type.pointee.args[0].pointee) + print(llfun) + print(llresultslot) + self.llbuilder.call(llfun, [llresultslot] + llargs) + llresult = self.llbuilder.load(llresultslot) + + self.llbuilder.call(self.llbuiltin("llvm.stackrestore"), [llstackptr]) + + return llresult + else: + return self.llbuilder.call(llfun, llargs, + name=insn.name) def process_Invoke(self, insn): llnormalblock = self.map(insn.normal_target()) @@ -937,7 +981,11 @@ class LLVMIRGenerator: if builtins.is_none(insn.value().type): return self.llbuilder.ret_void() else: - return self.llbuilder.ret(self.map(insn.value())) + if self.llfunction.type.pointee.__has_sret: + self.llbuilder.store(self.map(insn.value()), self.llfunction.args[0]) + return self.llbuilder.ret_void() + else: + return self.llbuilder.ret(self.map(insn.value())) def process_Unreachable(self, insn): return self.llbuilder.unreachable() From b39e76ae28ea425a2c28876306add4680b713212 Mon Sep 17 00:00:00 2001 From: whitequark Date: Sat, 22 Aug 2015 12:22:26 -0700 Subject: [PATCH 229/369] Remove debug print. --- artiq/compiler/transforms/artiq_ir_generator.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/artiq/compiler/transforms/artiq_ir_generator.py b/artiq/compiler/transforms/artiq_ir_generator.py index b6cd2863e..6b38f6af2 100644 --- a/artiq/compiler/transforms/artiq_ir_generator.py +++ b/artiq/compiler/transforms/artiq_ir_generator.py @@ -714,8 +714,6 @@ class ARTIQIRGenerator(algorithm.Visitor): if node.attr not in obj.type.find().attributes: # A class attribute. Get the constructor (class object) and # extract the attribute from it. - print(node) - print(obj) constr_type = obj.type.constructor constr = self.append(ir.GetConstructor(self._env_for(constr_type.name), constr_type.name, constr_type, From 0e26cfb66eef70be84aaa9c8f560c479fb7bf92f Mon Sep 17 00:00:00 2001 From: whitequark Date: Sat, 22 Aug 2015 13:31:09 -0700 Subject: [PATCH 230/369] LocalAccessValidator: relax restrictions to accept def f(); def g(). --- artiq/compiler/module.py | 2 +- .../transforms/dead_code_eliminator.py | 9 ++-- artiq/compiler/validators/local_access.py | 48 +++++++++++++------ lit-test/test/integration/finally.py | 44 ++++++++--------- lit-test/test/local_access/invalid_closure.py | 15 ++++++ .../{invalid.py => invalid_flow.py} | 7 --- 6 files changed, 77 insertions(+), 48 deletions(-) create mode 100644 lit-test/test/local_access/invalid_closure.py rename lit-test/test/local_access/{invalid.py => invalid_flow.py} (66%) diff --git a/artiq/compiler/module.py b/artiq/compiler/module.py index f6285540a..365760bb7 100644 --- a/artiq/compiler/module.py +++ b/artiq/compiler/module.py @@ -60,7 +60,7 @@ class Module: escape_validator.visit(src.typedtree) self.artiq_ir = artiq_ir_generator.visit(src.typedtree) dead_code_eliminator.process(self.artiq_ir) - # local_access_validator.process(self.artiq_ir) + local_access_validator.process(self.artiq_ir) def build_llvm_ir(self, target): """Compile the module to LLVM IR for the specified target.""" diff --git a/artiq/compiler/transforms/dead_code_eliminator.py b/artiq/compiler/transforms/dead_code_eliminator.py index 1159dc0ca..dfe5ccf6c 100644 --- a/artiq/compiler/transforms/dead_code_eliminator.py +++ b/artiq/compiler/transforms/dead_code_eliminator.py @@ -14,10 +14,11 @@ class DeadCodeEliminator: self.process_function(func) def process_function(self, func): - for block in func.basic_blocks: - if not any(block.predecessors()) and \ - not any([isinstance(use, ir.SetLocal) for use in block.uses]) and \ - block != func.entry(): + for block in list(func.basic_blocks): + if not any(block.predecessors()) and block != func.entry(): + for use in set(block.uses): + if isinstance(use, ir.SetLocal): + use.erase() self.remove_block(block) def remove_block(self, block): diff --git a/artiq/compiler/validators/local_access.py b/artiq/compiler/validators/local_access.py index fb68b54d4..156a26e05 100644 --- a/artiq/compiler/validators/local_access.py +++ b/artiq/compiler/validators/local_access.py @@ -16,11 +16,13 @@ class LocalAccessValidator: self.process_function(func) def process_function(self, func): - # Find all environments allocated in this func. - environments = [] + # 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 ".") @@ -82,6 +84,7 @@ class LocalAccessValidator: # 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 in insn.var_name: env, var_name = insn.environment(), insn.var_name @@ -91,24 +94,41 @@ class LocalAccessValidator: 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)) - # Creating a closure has no side effects. However, using a closure does. - for operand in insn.operands: - if isinstance(operand, ir.Closure): - env = operand.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]: - # A closure would capture this variable while it is not always - # initialized. Note that this check is transitive. - self._uninitialized_access(operand, 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]: + # 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 diff --git a/lit-test/test/integration/finally.py b/lit-test/test/integration/finally.py index d418fbbe5..e629602ad 100644 --- a/lit-test/test/integration/finally.py +++ b/lit-test/test/integration/finally.py @@ -13,11 +13,6 @@ def f(): print("f-finally") print("f-out") -# CHECK-L: f-try -# CHECK-L: f-finally -# CHECK-L: f-out -f() - def g(): x = True while x: @@ -29,11 +24,6 @@ def g(): print("g-finally") print("g-out") -# CHECK-L: g-try -# CHECK-L: g-finally -# CHECK-L: g-out -g() - def h(): try: print("h-try") @@ -43,12 +33,6 @@ def h(): print("h-out") return 20 -# CHECK-L: h-try -# CHECK-L: h-finally -# CHECK-NOT-L: h-out -# CHECK-L: h 10 -print("h", h()) - def i(): try: print("i-try") @@ -59,12 +43,6 @@ def i(): print("i-out") return 20 -# CHECK-L: i-try -# CHECK-L: i-finally -# CHECK-NOT-L: i-out -# CHECK-L: i 30 -print("i", i()) - def j(): try: print("j-try") @@ -72,6 +50,28 @@ def j(): 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 diff --git a/lit-test/test/local_access/invalid_closure.py b/lit-test/test/local_access/invalid_closure.py new file mode 100644 index 000000000..75948fb57 --- /dev/null +++ b/lit-test/test/local_access/invalid_closure.py @@ -0,0 +1,15 @@ +# 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/lit-test/test/local_access/invalid.py b/lit-test/test/local_access/invalid_flow.py similarity index 66% rename from lit-test/test/local_access/invalid.py rename to lit-test/test/local_access/invalid_flow.py index fabd6e9a8..7b99958b4 100644 --- a/lit-test/test/local_access/invalid.py +++ b/lit-test/test/local_access/invalid_flow.py @@ -18,10 +18,3 @@ else: t = 1 # CHECK-L: ${LINE:+1}: error: variable 't' is not always initialized -t - -# 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 From a557445e0536cfb1788e29528fd32ef4beae71b9 Mon Sep 17 00:00:00 2001 From: whitequark Date: Sat, 22 Aug 2015 13:56:17 -0700 Subject: [PATCH 231/369] LocalAccessValidator: assume variables with "$" in name are internal. Internal variables are assumed to be scoped correctly (including not being accessed uninitialized). This was changed from "." because artiq.compiler.embedding uses "." in module prefix of function names. --- artiq/compiler/embedding.py | 4 +-- artiq/compiler/ir.py | 10 +++--- .../compiler/transforms/artiq_ir_generator.py | 34 +++++++++---------- .../compiler/transforms/llvm_ir_generator.py | 12 +++---- artiq/compiler/validators/local_access.py | 9 +++-- 5 files changed, 36 insertions(+), 33 deletions(-) diff --git a/artiq/compiler/embedding.py b/artiq/compiler/embedding.py index ec1b629e3..afbce7820 100644 --- a/artiq/compiler/embedding.py +++ b/artiq/compiler/embedding.py @@ -366,11 +366,11 @@ class Stitcher: if syscall is None: function_type = types.TRPCFunction(arg_types, optarg_types, ret_type, service=self._map(function)) - function_name = "__rpc_{}__".format(function_type.service) + function_name = "rpc${}".format(function_type.service) else: function_type = types.TCFunction(arg_types, ret_type, name=syscall) - function_name = "__ffi_{}__".format(function_type.name) + function_name = "ffi${}".format(function_type.name) self.globals[function_name] = function_type self.functions[function] = function_name diff --git a/artiq/compiler/ir.py b/artiq/compiler/ir.py index 90d5ef1a1..1861d110e 100644 --- a/artiq/compiler/ir.py +++ b/artiq/compiler/ir.py @@ -465,7 +465,7 @@ class TEnvironment(types.TMono): def __init__(self, vars, outer=None): if outer is not None: assert isinstance(outer, TEnvironment) - env = OrderedDict({".outer": outer}) + env = OrderedDict({"$outer": outer}) env.update(vars) else: env = OrderedDict(vars) @@ -475,14 +475,14 @@ class TEnvironment(types.TMono): 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) + 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() + if "$outer" in self.params: + return self.params["$outer"].outermost() else: return self diff --git a/artiq/compiler/transforms/artiq_ir_generator.py b/artiq/compiler/transforms/artiq_ir_generator.py index 6b38f6af2..fd1be222f 100644 --- a/artiq/compiler/transforms/artiq_ir_generator.py +++ b/artiq/compiler/transforms/artiq_ir_generator.py @@ -141,7 +141,7 @@ class ARTIQIRGenerator(algorithm.Visitor): env = self.append(ir.Alloc([], ir.TEnvironment(node.typing_env), name="env")) old_env, self.current_env = self.current_env, env - priv_env = self.append(ir.Alloc([], ir.TEnvironment({ ".return": typ.ret }), + priv_env = self.append(ir.Alloc([], ir.TEnvironment({ "$return": typ.ret }), name="privenv")) old_priv_env, self.current_private_env = self.current_private_env, priv_env @@ -181,7 +181,7 @@ class ARTIQIRGenerator(algorithm.Visitor): 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.current_env.type.add("default$" + arg_name, default.type) self.append(ir.SetLocal(self.current_env, env_default_name, default)) defaults.append(env_default_name) @@ -217,11 +217,11 @@ class ARTIQIRGenerator(algorithm.Visitor): old_env, self.current_env = self.current_env, env if not is_lambda: - priv_env = self.append(ir.Alloc([], ir.TEnvironment({ ".return": typ.ret }), + priv_env = self.append(ir.Alloc([], ir.TEnvironment({ "$return": typ.ret }), name="privenv")) old_priv_env, self.current_private_env = self.current_private_env, priv_env - self.append(ir.SetLocal(env, ".outer", env_arg)) + 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, env_default_name) in enumerate(zip(typ.optargs, defaults)): @@ -267,7 +267,7 @@ class ARTIQIRGenerator(algorithm.Visitor): 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.SetLocal(self.current_private_env, "$return", return_value)) self.append(ir.Branch(self.return_target)) def visit_Expr(self, node): @@ -516,30 +516,30 @@ class ARTIQIRGenerator(algorithm.Visitor): if any(node.finalbody): # k for continuation - final_state = self.append(ir.Alloc([], ir.TEnvironment({ ".k": ir.TBasicBlock() }))) + final_state = self.append(ir.Alloc([], ir.TEnvironment({ "$k": ir.TBasicBlock() }))) final_targets = [] if self.break_target is not None: break_proxy = self.add_block("try.break") old_break, self.break_target = self.break_target, break_proxy - break_proxy.append(ir.SetLocal(final_state, ".k", old_break)) + break_proxy.append(ir.SetLocal(final_state, "$k", old_break)) final_targets.append(old_break) if self.continue_target is not None: continue_proxy = self.add_block("try.continue") old_continue, self.continue_target = self.continue_target, continue_proxy - continue_proxy.append(ir.SetLocal(final_state, ".k", old_continue)) + continue_proxy.append(ir.SetLocal(final_state, "$k", old_continue)) final_targets.append(old_continue) return_proxy = self.add_block("try.return") old_return, self.return_target = self.return_target, return_proxy if old_return is not None: - return_proxy.append(ir.SetLocal(final_state, ".k", old_return)) + return_proxy.append(ir.SetLocal(final_state, "$k", old_return)) final_targets.append(old_return) else: return_action = self.add_block("try.doreturn") - value = return_action.append(ir.GetLocal(self.current_private_env, ".return")) + value = return_action.append(ir.GetLocal(self.current_private_env, "$return")) return_action.append(ir.Return(value)) - return_proxy.append(ir.SetLocal(final_state, ".k", return_action)) + return_proxy.append(ir.SetLocal(final_state, "$k", return_action)) final_targets.append(return_action) body = self.add_block("try.body") @@ -607,19 +607,19 @@ class ARTIQIRGenerator(algorithm.Visitor): return_proxy.append(ir.Branch(finalizer)) if not body.is_terminated(): - body.append(ir.SetLocal(final_state, ".k", tail)) + body.append(ir.SetLocal(final_state, "$k", tail)) body.append(ir.Branch(finalizer)) - cleanup.append(ir.SetLocal(final_state, ".k", reraise)) + cleanup.append(ir.SetLocal(final_state, "$k", reraise)) cleanup.append(ir.Branch(finalizer)) for handler, post_handler in handlers: if not post_handler.is_terminated(): - post_handler.append(ir.SetLocal(final_state, ".k", tail)) + post_handler.append(ir.SetLocal(final_state, "$k", tail)) post_handler.append(ir.Branch(finalizer)) if not post_finalizer.is_terminated(): - dest = post_finalizer.append(ir.GetLocal(final_state, ".k")) + dest = post_finalizer.append(ir.GetLocal(final_state, "$k")) post_finalizer.append(ir.IndirectBranch(dest, final_targets)) else: if not body.is_terminated(): @@ -961,7 +961,7 @@ class ARTIQIRGenerator(algorithm.Visitor): 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)) + self.append(ir.SetLocal(env, "$outer", old_env)) def body_gen(index): elt = self.iterable_get(iterable, index) @@ -1483,7 +1483,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), diff --git a/artiq/compiler/transforms/llvm_ir_generator.py b/artiq/compiler/transforms/llvm_ir_generator.py index 2956020c8..0c1d8d5ec 100644 --- a/artiq/compiler/transforms/llvm_ir_generator.py +++ b/artiq/compiler/transforms/llvm_ir_generator.py @@ -495,10 +495,10 @@ class LLVMIRGenerator: var_index = list(env_ty.params.keys()).index(var_name) return self.llbuilder.gep(llenv, [self.llindex(0), self.llindex(var_index)]) else: - outer_index = list(env_ty.params.keys()).index(".outer") + outer_index = list(env_ty.params.keys()).index("$outer") llptr = self.llbuilder.gep(llenv, [self.llindex(0), self.llindex(outer_index)]) llouterenv = self.llbuilder.load(llptr) - return self.llptr_to_var(llouterenv, env_ty.params[".outer"], var_name) + return self.llptr_to_var(llouterenv, env_ty.params["$outer"], var_name) def process_GetLocal(self, insn): env = insn.environment() @@ -519,7 +519,7 @@ class LLVMIRGenerator: 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. + # We fixup the type on assignment into the "$outer" slot. assert isinstance(insn.value(), ir.EnvironmentArgument) llvalue = self.llbuilder.bitcast(llvalue, llptr.type.pointee) return self.llbuilder.store(llvalue, llptr) @@ -740,11 +740,11 @@ class LLVMIRGenerator: 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") + 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)]) llouterenv = self.llbuilder.load(llptr) - return self.llptr_to_var(llouterenv, env_ty.params[".outer"], var_name) + return self.llptr_to_var(llouterenv, env_ty.params["$outer"], var_name) else: return llenv diff --git a/artiq/compiler/validators/local_access.py b/artiq/compiler/validators/local_access.py index 156a26e05..f3429140e 100644 --- a/artiq/compiler/validators/local_access.py +++ b/artiq/compiler/validators/local_access.py @@ -7,6 +7,9 @@ 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 @@ -85,8 +88,8 @@ class LocalAccessValidator: return None set_local_in_this_frame = False - if isinstance(insn, (ir.SetLocal, ir.GetLocal)) and \ - "." not in insn.var_name: + 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. @@ -124,7 +127,7 @@ class LocalAccessValidator: # 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]: + 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, From 526d7c4e462dd6463f7f4258e9bfc84220330df4 Mon Sep 17 00:00:00 2001 From: whitequark Date: Sat, 22 Aug 2015 14:01:55 -0700 Subject: [PATCH 232/369] Fix a typo. --- artiq/compiler/transforms/llvm_ir_generator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/artiq/compiler/transforms/llvm_ir_generator.py b/artiq/compiler/transforms/llvm_ir_generator.py index 0c1d8d5ec..80aabdc09 100644 --- a/artiq/compiler/transforms/llvm_ir_generator.py +++ b/artiq/compiler/transforms/llvm_ir_generator.py @@ -832,7 +832,7 @@ class LLVMIRGenerator: arg.loc) diag = diagnostic.Diagnostic("error", "type {type} is not supported in remote procedure calls", - {"type": printer.name(arg.typ)}, + {"type": printer.name(arg.type)}, arg.loc) self.engine.process(diag) tag += self._rpc_tag(arg.type, arg_error_handler) From 9b9fa1ab7c6bce174e4b1f521a9c5248e0402e5d Mon Sep 17 00:00:00 2001 From: whitequark Date: Tue, 25 Aug 2015 21:56:01 -0700 Subject: [PATCH 233/369] Allow embedding and RPC sending host objects. --- artiq/compiler/embedding.py | 79 +++++++++++++------ artiq/compiler/ir.py | 17 ++++ artiq/compiler/module.py | 6 +- .../compiler/transforms/artiq_ir_generator.py | 3 + .../compiler/transforms/llvm_ir_generator.py | 54 ++++++++++++- artiq/coredevice/comm_generic.py | 32 ++++---- artiq/coredevice/core.py | 6 +- soc/runtime/session.c | 4 +- 8 files changed, 154 insertions(+), 47 deletions(-) diff --git a/artiq/compiler/embedding.py b/artiq/compiler/embedding.py index afbce7820..5232c6293 100644 --- a/artiq/compiler/embedding.py +++ b/artiq/compiler/embedding.py @@ -14,11 +14,30 @@ from . import types, builtins, asttyped, prelude from .transforms import ASTTypedRewriter, Inferencer, IntMonomorphizer +class ObjectMap: + def __init__(self): + self.current_key = 0 + self.forward_map = {} + self.reverse_map = {} + + def store(self, obj_ref): + obj_id = id(obj_ref) + if obj_id in self.reverse_map: + return self.reverse_map[obj_id] + + self.current_key += 1 + self.forward_map[self.current_key] = obj_ref + self.reverse_map[obj_id] = self.current_key + return self.current_key + + def retrieve(self, obj_key): + return self.forward_map[obj_key] + class ASTSynthesizer: - def __init__(self, expanded_from=None): + def __init__(self, type_map, expanded_from=None): self.source = "" self.source_buffer = source.Buffer(self.source, "") - self.expanded_from = expanded_from + self.type_map, self.expanded_from = type_map, expanded_from def finalize(self): self.source_buffer.source = self.source @@ -63,8 +82,32 @@ class ASTSynthesizer: begin_loc=begin_loc, end_loc=end_loc, loc=begin_loc.join(end_loc)) else: - raise "no" - # return asttyped.QuoteT(value=value, type=types.TVar()) + if isinstance(value, type): + typ = value + else: + typ = type(value) + + if typ in self.type_map: + instance_type, constructor_type = self.type_map[typ] + else: + instance_type = types.TInstance("{}.{}".format(typ.__module__, typ.__name__)) + instance_type.attributes['__objectid__'] = builtins.TInt(types.TValue(32)) + + constructor_type = types.TConstructor(instance_type) + constructor_type.attributes['__objectid__'] = builtins.TInt(types.TValue(32)) + + self.type_map[typ] = instance_type, constructor_type + + quote_loc = self._add('`') + repr_loc = self._add(repr(value)) + unquote_loc = self._add('`') + + if isinstance(value, type): + return asttyped.QuoteT(value=value, type=constructor_type, + loc=quote_loc.join(unquote_loc)) + else: + return asttyped.QuoteT(value=value, type=instance_type, + loc=quote_loc.join(unquote_loc)) def call(self, function_node, args, kwargs): """ @@ -108,13 +151,14 @@ class ASTSynthesizer: loc=name_loc.join(end_loc)) class StitchingASTTypedRewriter(ASTTypedRewriter): - def __init__(self, engine, prelude, globals, host_environment, quote_function): + def __init__(self, engine, prelude, globals, host_environment, quote_function, type_map): super().__init__(engine, prelude) self.globals = globals self.env_stack.append(self.globals) self.host_environment = host_environment self.quote_function = quote_function + self.type_map = type_map def visit_Name(self, node): typ = super()._try_find_name(node.id) @@ -136,7 +180,7 @@ class StitchingASTTypedRewriter(ASTTypedRewriter): else: # It's just a value. Quote it. - synthesizer = ASTSynthesizer(expanded_from=node.loc) + synthesizer = ASTSynthesizer(expanded_from=node.loc, type_map=self.type_map) node = synthesizer.quote(value) synthesizer.finalize() return node @@ -160,19 +204,8 @@ class Stitcher: self.functions = {} - self.next_rpc = 0 - self.rpc_map = {} - self.inverse_rpc_map = {} - - def _map(self, obj): - obj_id = id(obj) - if obj_id in self.inverse_rpc_map: - return self.inverse_rpc_map[obj_id] - - self.next_rpc += 1 - self.rpc_map[self.next_rpc] = obj - self.inverse_rpc_map[obj_id] = self.next_rpc - return self.next_rpc + self.object_map = ObjectMap() + self.type_map = {} def finalize(self): inferencer = Inferencer(engine=self.engine) @@ -229,7 +262,7 @@ class Stitcher: asttyped_rewriter = StitchingASTTypedRewriter( engine=self.engine, prelude=self.prelude, globals=self.globals, host_environment=host_environment, - quote_function=self._quote_function) + quote_function=self._quote_function, type_map=self.type_map) return asttyped_rewriter.visit(function_node) def _function_loc(self, function): @@ -291,7 +324,7 @@ class Stitcher: # 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. - synthesizer = ASTSynthesizer() + synthesizer = ASTSynthesizer(type_map=self.type_map) ast = synthesizer.quote(param.default) synthesizer.finalize() @@ -365,7 +398,7 @@ class Stitcher: if syscall is None: function_type = types.TRPCFunction(arg_types, optarg_types, ret_type, - service=self._map(function)) + service=self.object_map.store(function)) function_name = "rpc${}".format(function_type.service) else: function_type = types.TCFunction(arg_types, ret_type, @@ -409,7 +442,7 @@ class Stitcher: # We synthesize source code for the initial call so that # diagnostics would have something meaningful to display to the user. - synthesizer = ASTSynthesizer() + synthesizer = ASTSynthesizer(type_map=self.type_map) call_node = synthesizer.call(function_node, args, kwargs) synthesizer.finalize() self.typedtree.append(call_node) diff --git a/artiq/compiler/ir.py b/artiq/compiler/ir.py index 1861d110e..f20357ed8 100644 --- a/artiq/compiler/ir.py +++ b/artiq/compiler/ir.py @@ -901,6 +901,23 @@ class Select(Instruction): 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 opcode(self): + return "quote({})".format(repr(self.value)) + class Branch(Terminator): """ An unconditional branch instruction. diff --git a/artiq/compiler/module.py b/artiq/compiler/module.py index 365760bb7..dbcaf2cd7 100644 --- a/artiq/compiler/module.py +++ b/artiq/compiler/module.py @@ -19,6 +19,8 @@ class Source: else: self.engine = engine + self.object_map = None + self.name, _ = os.path.splitext(os.path.basename(source_buffer.name)) asttyped_rewriter = transforms.ASTTypedRewriter(engine=engine, @@ -42,6 +44,7 @@ class Source: class Module: def __init__(self, src): self.engine = src.engine + self.object_map = src.object_map int_monomorphizer = transforms.IntMonomorphizer(engine=self.engine) inferencer = transforms.Inferencer(engine=self.engine) @@ -65,7 +68,8 @@ class Module: 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) + module_name=self.name, target=target, + object_map=self.object_map) return llvm_ir_generator.process(self.artiq_ir) def entry_point(self): diff --git a/artiq/compiler/transforms/artiq_ir_generator.py b/artiq/compiler/transforms/artiq_ir_generator.py index fd1be222f..6f6b626f3 100644 --- a/artiq/compiler/transforms/artiq_ir_generator.py +++ b/artiq/compiler/transforms/artiq_ir_generator.py @@ -1474,6 +1474,9 @@ class ARTIQIRGenerator(algorithm.Visitor): self.current_block = after_invoke return invoke + def visit_QuoteT(self, node): + return self.append(ir.Quote(node.value, node.type)) + def instrument_assert(self, node, value): if self.current_assert_env is not None: if isinstance(value, ir.Constant): diff --git a/artiq/compiler/transforms/llvm_ir_generator.py b/artiq/compiler/transforms/llvm_ir_generator.py index 80aabdc09..4e5af87a6 100644 --- a/artiq/compiler/transforms/llvm_ir_generator.py +++ b/artiq/compiler/transforms/llvm_ir_generator.py @@ -159,15 +159,17 @@ class DebugInfoEmitter: class LLVMIRGenerator: - def __init__(self, engine, module_name, target): + def __init__(self, engine, module_name, target, object_map): self.engine = engine self.target = target + self.object_map = object_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.llfunction = None self.llmap = {} + self.llobject_map = {} self.phis = [] self.debug_info_emitter = DebugInfoEmitter(self.llmodule) @@ -815,6 +817,8 @@ class LLVMIRGenerator: elif ir.is_option(typ): return b"o" + self._rpc_tag(typ.params["inner"], error_handler) + elif '__objectid__' in typ.attributes: + return b"O" else: error_handler(typ) @@ -960,6 +964,54 @@ class LLVMIRGenerator: return self.llbuilder.invoke(llfun, llargs, llnormalblock, llunwindblock, name=insn.name) + def _quote(self, value, typ, path): + value_id = id(value) + if value_id in self.llobject_map: + return self.llobject_map[value_id] + + global_name = "" + llty = self.llty_of_type(typ) + + if types.is_constructor(typ) or types.is_instance(typ): + llfields = [] + for attr in typ.attributes: + if attr == "__objectid__": + objectid = self.object_map.store(value) + llfields.append(ll.Constant(lli32, objectid)) + global_name = "object.{}".format(objectid) + else: + llfields.append(self._quote(getattr(value, attr), typ.attributes[attr], + path + [attr])) + + llvalue = ll.Constant.literal_struct(llfields) + elif builtins.is_none(typ): + assert value is None + return self.llconst_of_const(value) + elif builtins.is_bool(typ): + assert value in (True, False) + return self.llconst_of_const(value) + elif builtins.is_int(typ): + assert isinstance(value, int) + return self.llconst_of_const(value) + elif builtins.is_float(typ): + assert isinstance(value, float) + return self.llconst_of_const(value) + elif builtins.is_str(typ): + assert isinstance(value, (str, bytes)) + return self.llconst_of_const(value) + else: + assert False + + llconst = ll.GlobalVariable(self.llmodule, llvalue.type, global_name) + llconst.initializer = llvalue + llconst.linkage = "private" + self.llobject_map[value_id] = llconst + return llconst + + def process_Quote(self, insn): + assert self.object_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())) diff --git a/artiq/coredevice/comm_generic.py b/artiq/coredevice/comm_generic.py index 3f1a188d3..b1967b352 100644 --- a/artiq/coredevice/comm_generic.py +++ b/artiq/coredevice/comm_generic.py @@ -282,13 +282,13 @@ class CommGeneric: _rpc_sentinel = object() # See session.c:{send,receive}_rpc_value and llvm_ir_generator.py:_rpc_tag. - def _receive_rpc_value(self, rpc_map): + def _receive_rpc_value(self, object_map): tag = chr(self._read_int8()) if tag == "\x00": return self._rpc_sentinel elif tag == "t": length = self._read_int8() - return tuple(self._receive_rpc_value(rpc_map) for _ in range(length)) + return tuple(self._receive_rpc_value(object_map) for _ in range(length)) elif tag == "n": return None elif tag == "b": @@ -307,25 +307,25 @@ class CommGeneric: return self._read_string() elif tag == "l": length = self._read_int32() - return [self._receive_rpc_value(rpc_map) for _ in range(length)] + return [self._receive_rpc_value(object_map) for _ in range(length)] elif tag == "r": - start = self._receive_rpc_value(rpc_map) - stop = self._receive_rpc_value(rpc_map) - step = self._receive_rpc_value(rpc_map) + start = self._receive_rpc_value(object_map) + stop = self._receive_rpc_value(object_map) + step = self._receive_rpc_value(object_map) return range(start, stop, step) elif tag == "o": present = self._read_int8() if present: - return self._receive_rpc_value(rpc_map) + return self._receive_rpc_value(object_map) elif tag == "O": - return rpc_map[self._read_int32()] + return object_map.retrieve(self._read_int32()) else: raise IOError("Unknown RPC value tag: {}".format(repr(tag))) - def _receive_rpc_args(self, rpc_map): + def _receive_rpc_args(self, object_map): args = [] while True: - value = self._receive_rpc_value(rpc_map) + value = self._receive_rpc_value(object_map) if value is self._rpc_sentinel: return args args.append(value) @@ -410,20 +410,20 @@ class CommGeneric: else: raise IOError("Unknown RPC value tag: {}".format(repr(tag))) - def _serve_rpc(self, rpc_map): + def _serve_rpc(self, object_map): service = self._read_int32() - args = self._receive_rpc_args(rpc_map) + args = self._receive_rpc_args(object_map) return_tags = self._read_bytes() logger.debug("rpc service: %d %r -> %s", service, args, return_tags) try: - result = rpc_map[service](*args) + result = object_map.retrieve(service)(*args) logger.debug("rpc service: %d %r == %r", service, args, result) self._write_header(_H2DMsgType.RPC_REPLY) self._write_bytes(return_tags) self._send_rpc_value(bytearray(return_tags), result, result, - rpc_map[service]) + object_map.retrieve(service)) self._write_flush() except core_language.ARTIQException as exn: logger.debug("rpc service: %d %r ! %r", service, args, exn) @@ -473,11 +473,11 @@ class CommGeneric: [(filename, line, column, function, None)] raise core_language.ARTIQException(name, message, params, traceback) - def serve(self, rpc_map, symbolizer): + def serve(self, object_map, symbolizer): while True: self._read_header() if self._read_type == _D2HMsgType.RPC_REQUEST: - self._serve_rpc(rpc_map) + self._serve_rpc(object_map) elif self._read_type == _D2HMsgType.KERNEL_EXCEPTION: self._serve_exception(symbolizer) else: diff --git a/artiq/coredevice/core.py b/artiq/coredevice/core.py index 6246f6301..e02b1ca47 100644 --- a/artiq/coredevice/core.py +++ b/artiq/coredevice/core.py @@ -45,14 +45,14 @@ class Core: library = target.compile_and_link([module]) stripped_library = target.strip(library) - return stitcher.rpc_map, stripped_library, \ + return stitcher.object_map, stripped_library, \ lambda addresses: target.symbolize(library, addresses) except diagnostic.Error as error: print("\n".join(error.diagnostic.render(colored=True)), file=sys.stderr) raise CompileError() from error def run(self, function, args, kwargs): - rpc_map, kernel_library, symbolizer = self.compile(function, args, kwargs) + object_map, kernel_library, symbolizer = self.compile(function, args, kwargs) if self.first_run: self.comm.check_ident() @@ -61,7 +61,7 @@ class Core: self.comm.load(kernel_library) self.comm.run() - self.comm.serve(rpc_map, symbolizer) + self.comm.serve(object_map, symbolizer) @kernel def get_rtio_counter_mu(self): diff --git a/soc/runtime/session.c b/soc/runtime/session.c index 2ea0c0e8e..ff6629f4f 100644 --- a/soc/runtime/session.c +++ b/soc/runtime/session.c @@ -816,9 +816,7 @@ static int send_rpc_value(const char **tag, void **value) case 'O': { // host object struct { uint32_t id; } **object = *value; - - if(!out_packet_int32((*object)->id)) - return 0; + return out_packet_int32((*object)->id); } default: From 422208a0e9a333893400ac1405072229f59fb7c5 Mon Sep 17 00:00:00 2001 From: whitequark Date: Tue, 25 Aug 2015 22:05:34 -0700 Subject: [PATCH 234/369] Fix copy-paste error. --- artiq/compiler/embedding.py | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/artiq/compiler/embedding.py b/artiq/compiler/embedding.py index 5232c6293..8a9ee287a 100644 --- a/artiq/compiler/embedding.py +++ b/artiq/compiler/embedding.py @@ -446,18 +446,3 @@ class Stitcher: call_node = synthesizer.call(function_node, args, kwargs) synthesizer.finalize() self.typedtree.append(call_node) - - def finalize(self): - inferencer = Inferencer(engine=self.engine) - - # Iterate inference to fixed point. - self.inference_finished = False - while not self.inference_finished: - self.inference_finished = True - inferencer.visit(self.typedtree) - - # 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)) From cb225269ff1d49604616cf1598d20a04cffa2012 Mon Sep 17 00:00:00 2001 From: whitequark Date: Thu, 27 Aug 2015 05:01:04 -0500 Subject: [PATCH 235/369] Allow accessing attributes of embedded host objects. --- artiq/compiler/embedding.py | 121 +++++++++++++++--- artiq/compiler/transforms/inferencer.py | 12 +- .../compiler/transforms/llvm_ir_generator.py | 55 ++++---- artiq/compiler/types.py | 3 + 4 files changed, 151 insertions(+), 40 deletions(-) diff --git a/artiq/compiler/embedding.py b/artiq/compiler/embedding.py index 8a9ee287a..d6e4403a2 100644 --- a/artiq/compiler/embedding.py +++ b/artiq/compiler/embedding.py @@ -6,12 +6,13 @@ annotated as ``@kernel`` when they are referenced. """ import os, re, linecache, inspect -from collections import OrderedDict +from collections import OrderedDict, defaultdict from pythonparser import ast, source, diagnostic, parse_buffer from . import types, builtins, asttyped, prelude from .transforms import ASTTypedRewriter, Inferencer, IntMonomorphizer +from .validators import MonomorphismValidator class ObjectMap: @@ -34,10 +35,11 @@ class ObjectMap: return self.forward_map[obj_key] class ASTSynthesizer: - def __init__(self, type_map, expanded_from=None): + def __init__(self, type_map, value_map, expanded_from=None): self.source = "" self.source_buffer = source.Buffer(self.source, "") - self.type_map, self.expanded_from = type_map, expanded_from + self.type_map, self.value_map = type_map, value_map + self.expanded_from = expanded_from def finalize(self): self.source_buffer.source = self.source @@ -82,6 +84,11 @@ class ASTSynthesizer: begin_loc=begin_loc, end_loc=end_loc, loc=begin_loc.join(end_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: @@ -98,16 +105,14 @@ class ASTSynthesizer: self.type_map[typ] = instance_type, constructor_type - quote_loc = self._add('`') - repr_loc = self._add(repr(value)) - unquote_loc = self._add('`') - if isinstance(value, type): + self.value_map[constructor_type].append((value, loc)) return asttyped.QuoteT(value=value, type=constructor_type, - loc=quote_loc.join(unquote_loc)) + loc=loc) else: + self.value_map[instance_type].append((value, loc)) return asttyped.QuoteT(value=value, type=instance_type, - loc=quote_loc.join(unquote_loc)) + loc=loc) def call(self, function_node, args, kwargs): """ @@ -151,7 +156,8 @@ class ASTSynthesizer: loc=name_loc.join(end_loc)) class StitchingASTTypedRewriter(ASTTypedRewriter): - def __init__(self, engine, prelude, globals, host_environment, quote_function, type_map): + def __init__(self, engine, prelude, globals, host_environment, quote_function, + type_map, value_map): super().__init__(engine, prelude) self.globals = globals self.env_stack.append(self.globals) @@ -159,6 +165,7 @@ class StitchingASTTypedRewriter(ASTTypedRewriter): self.host_environment = host_environment self.quote_function = quote_function self.type_map = type_map + self.value_map = value_map def visit_Name(self, node): typ = super()._try_find_name(node.id) @@ -180,7 +187,9 @@ class StitchingASTTypedRewriter(ASTTypedRewriter): else: # It's just a value. Quote it. - synthesizer = ASTSynthesizer(expanded_from=node.loc, type_map=self.type_map) + synthesizer = ASTSynthesizer(expanded_from=node.loc, + type_map=self.type_map, + value_map=self.value_map) node = synthesizer.quote(value) synthesizer.finalize() return node @@ -190,6 +199,83 @@ class StitchingASTTypedRewriter(ASTTypedRewriter): node.loc) self.engine.process(diag) +class StitchingInferencer(Inferencer): + def __init__(self, engine, type_map, value_map): + super().__init__(engine) + self.type_map, self.value_map = type_map, value_map + + def visit_AttributeT(self, node): + self.generic_visit(node) + object_type = node.value.type.find() + + # 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. + # + # FIXME: We perform exhaustive checks of every known host object every + # time an attribute access is visited, which is potentially quadratic. + # This is done because it is simpler than performing the checks only when: + # * a previously unknown attribute is encountered, + # * a previously unknown host object is encountered; + # which would be the optimal solution. + for object_value, object_loc in self.value_map[object_type]: + if not hasattr(object_value, node.attr): + note = diagnostic.Diagnostic("note", + "attribute accessed here", {}, + node.loc) + diag = diagnostic.Diagnostic("error", + "host object does not have an attribute '{attr}'", + {"attr": node.attr}, + object_loc, notes=[note]) + self.engine.process(diag) + return + + # Figure out what ARTIQ type does the value of the attribute have. + # 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. + synthesizer = ASTSynthesizer(type_map=self.type_map, + value_map=self.value_map) + ast = synthesizer.quote(getattr(object_value, node.attr)) + synthesizer.finalize() + + def proxy_diagnostic(diag): + note = diagnostic.Diagnostic("note", + "expanded from here while trying to infer a type for an" + " attribute '{attr}' of a host object", + {"attr": node.attr}, + node.loc) + diag.notes.append(note) + + self.engine.process(diag) + + proxy_engine = diagnostic.Engine() + proxy_engine.process = proxy_diagnostic + Inferencer(engine=proxy_engine).visit(ast) + IntMonomorphizer(engine=proxy_engine).visit(ast) + MonomorphismValidator(engine=proxy_engine).visit(ast) + + if node.attr not in object_type.attributes: + # We just figured out what the type should be. Add it. + object_type.attributes[node.attr] = ast.type + elif object_type.attributes[node.attr] != ast.type: + # Does this conflict with an earlier guess? + printer = types.TypePrinter() + diag = diagnostic.Diagnostic("error", + "host object has an attribute of type {typea}, which is" + " different from previously inferred type {typeb}", + {"typea": printer.name(ast.type), + "typeb": printer.name(object_type.attributes[node.attr])}, + object_loc) + self.engine.process(diag) + + super().visit_AttributeT(node) + class Stitcher: def __init__(self, engine=None): if engine is None: @@ -206,9 +292,11 @@ class Stitcher: self.object_map = ObjectMap() self.type_map = {} + self.value_map = defaultdict(lambda: []) def finalize(self): - inferencer = Inferencer(engine=self.engine) + inferencer = StitchingInferencer(engine=self.engine, + type_map=self.type_map, value_map=self.value_map) # Iterate inference to fixed point. self.inference_finished = False @@ -262,7 +350,8 @@ class Stitcher: asttyped_rewriter = StitchingASTTypedRewriter( engine=self.engine, prelude=self.prelude, globals=self.globals, host_environment=host_environment, - quote_function=self._quote_function, type_map=self.type_map) + quote_function=self._quote_function, + type_map=self.type_map, value_map=self.value_map) return asttyped_rewriter.visit(function_node) def _function_loc(self, function): @@ -324,7 +413,8 @@ class Stitcher: # 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. - synthesizer = ASTSynthesizer(type_map=self.type_map) + synthesizer = ASTSynthesizer(type_map=self.type_map, + value_map=self.value_map) ast = synthesizer.quote(param.default) synthesizer.finalize() @@ -442,7 +532,8 @@ class Stitcher: # We synthesize source code for the initial call so that # diagnostics would have something meaningful to display to the user. - synthesizer = ASTSynthesizer(type_map=self.type_map) + synthesizer = ASTSynthesizer(type_map=self.type_map, + value_map=self.value_map) call_node = synthesizer.call(function_node, args, kwargs) synthesizer.finalize() self.typedtree.append(call_node) diff --git a/artiq/compiler/transforms/inferencer.py b/artiq/compiler/transforms/inferencer.py index 93a5c2c7e..119ffdab5 100644 --- a/artiq/compiler/transforms/inferencer.py +++ b/artiq/compiler/transforms/inferencer.py @@ -130,10 +130,20 @@ class Inferencer(algorithm.Visitor): self._unify(node.type, attr_type, node.loc, None) else: + if node.attr_loc.source_buffer == node.value.loc.source_buffer: + highlights, notes = [node.value.loc], [] + else: + # This happens when the object being accessed is embedded + # from the host program. + note = diagnostic.Diagnostic("note", + "object being accessed", {}, + node.value.loc) + highlights, notes = [], [note] + diag = diagnostic.Diagnostic("error", "type {type} does not have an attribute '{attr}'", {"type": types.TypePrinter().name(object_type), "attr": node.attr}, - node.attr_loc, [node.value.loc]) + node.attr_loc, highlights, notes) self.engine.process(diag) def _unify_iterable(self, element, collection): diff --git a/artiq/compiler/transforms/llvm_ir_generator.py b/artiq/compiler/transforms/llvm_ir_generator.py index 4e5af87a6..2e47b15b3 100644 --- a/artiq/compiler/transforms/llvm_ir_generator.py +++ b/artiq/compiler/transforms/llvm_ir_generator.py @@ -278,6 +278,27 @@ class LLVMIRGenerator: else: return llty.as_pointer() + def llstr_of_str(self, value, name=None, + linkage="private", unnamed_addr=True): + if isinstance(value, str): + assert "\0" not in value + as_bytes = (value + "\0").encode("utf-8") + else: + as_bytes = value + + if name is None: + name = self.llmodule.get_unique_name("str") + + llstr = self.llmodule.get_global(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: @@ -289,12 +310,6 @@ class LLVMIRGenerator: elif isinstance(const.value, (int, float)): return ll.Constant(llty, const.value) elif isinstance(const.value, (str, bytes)): - if isinstance(const.value, str): - assert "\0" not in const.value - as_bytes = (const.value + "\0").encode("utf-8") - else: - as_bytes = const.value - if ir.is_exn_typeinfo(const.type): # Exception typeinfo; should be merged with identical others name = "__artiq_exn_" + const.value @@ -302,20 +317,12 @@ class LLVMIRGenerator: unnamed_addr = False else: # Just a string - name = self.llmodule.get_unique_name("str") + name = None linkage = "private" unnamed_addr = True - llconst = self.llmodule.get_global(name) - if llconst is None: - llstrty = ll.ArrayType(lli8, len(as_bytes)) - llconst = ll.GlobalVariable(self.llmodule, llstrty, name) - llconst.global_constant = True - llconst.initializer = ll.Constant(llstrty, bytearray(as_bytes)) - llconst.linkage = linkage - llconst.unnamed_addr = unnamed_addr - - return llconst.bitcast(llptr) + return self.llstr_of_str(const.value, name=name, + linkage=linkage, unnamed_addr=unnamed_addr) else: assert False @@ -856,7 +863,7 @@ class LLVMIRGenerator: tag += self._rpc_tag(fun_type.ret, ret_error_handler) tag += b"\x00" - lltag = self.llconst_of_const(ir.Constant(tag + b"\x00", builtins.TStr())) + lltag = self.llstr_of_str(tag) llstackptr = self.llbuilder.call(self.llbuiltin("llvm.stacksave"), []) @@ -981,24 +988,24 @@ class LLVMIRGenerator: global_name = "object.{}".format(objectid) else: llfields.append(self._quote(getattr(value, attr), typ.attributes[attr], - path + [attr])) + lambda: path() + [attr])) llvalue = ll.Constant.literal_struct(llfields) elif builtins.is_none(typ): assert value is None - return self.llconst_of_const(value) + return ll.Constant.literal_struct([]) elif builtins.is_bool(typ): assert value in (True, False) - return self.llconst_of_const(value) + return ll.Constant(lli1, value) elif builtins.is_int(typ): assert isinstance(value, int) - return self.llconst_of_const(value) + return ll.Constant(ll.IntType(builtins.get_int_width(typ)), value) elif builtins.is_float(typ): assert isinstance(value, float) - return self.llconst_of_const(value) + return ll.Constant(lldouble, value) elif builtins.is_str(typ): assert isinstance(value, (str, bytes)) - return self.llconst_of_const(value) + return self.llstr_of_str(value) else: assert False diff --git a/artiq/compiler/types.py b/artiq/compiler/types.py index e1a3a406b..627fd4acb 100644 --- a/artiq/compiler/types.py +++ b/artiq/compiler/types.py @@ -320,6 +320,9 @@ class TBuiltin(Type): def __ne__(self, other): return not (self == other) + def __hash__(self): + return hash(self.name) + class TBuiltinFunction(TBuiltin): """ A type of a builtin function. From 04bd2421ad5ee944ef886090e778284d53de1e2b Mon Sep 17 00:00:00 2001 From: whitequark Date: Thu, 27 Aug 2015 05:44:56 -0500 Subject: [PATCH 236/369] compiler.embedding: dedent kernel functions before parsing. --- artiq/compiler/embedding.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/artiq/compiler/embedding.py b/artiq/compiler/embedding.py index d6e4403a2..c56ea7681 100644 --- a/artiq/compiler/embedding.py +++ b/artiq/compiler/embedding.py @@ -5,7 +5,7 @@ the references to the host objects and translates the functions annotated as ``@kernel`` when they are referenced. """ -import os, re, linecache, inspect +import os, re, linecache, inspect, textwrap from collections import OrderedDict, defaultdict from pythonparser import ast, source, diagnostic, parse_buffer @@ -316,7 +316,7 @@ class Stitcher: # Extract function source. embedded_function = function.artiq_embedded.function - source_code = inspect.getsource(embedded_function) + source_code = textwrap.dedent(inspect.getsource(embedded_function)) filename = embedded_function.__code__.co_filename module_name, _ = os.path.splitext(os.path.basename(filename)) first_line = embedded_function.__code__.co_firstlineno From c62b16d5e1b197e268800041cea42266eddfbb20 Mon Sep 17 00:00:00 2001 From: whitequark Date: Thu, 27 Aug 2015 05:53:18 -0500 Subject: [PATCH 237/369] compiler.embedding: support RPC functions as host attribute values. --- artiq/compiler/embedding.py | 61 +++++++++---------- .../compiler/transforms/llvm_ir_generator.py | 14 +++-- 2 files changed, 36 insertions(+), 39 deletions(-) diff --git a/artiq/compiler/embedding.py b/artiq/compiler/embedding.py index c56ea7681..424468bc1 100644 --- a/artiq/compiler/embedding.py +++ b/artiq/compiler/embedding.py @@ -12,7 +12,6 @@ from pythonparser import ast, source, diagnostic, parse_buffer from . import types, builtins, asttyped, prelude from .transforms import ASTTypedRewriter, Inferencer, IntMonomorphizer -from .validators import MonomorphismValidator class ObjectMap: @@ -156,16 +155,13 @@ class ASTSynthesizer: loc=name_loc.join(end_loc)) class StitchingASTTypedRewriter(ASTTypedRewriter): - def __init__(self, engine, prelude, globals, host_environment, quote_function, - type_map, value_map): + 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_function = quote_function - self.type_map = type_map - self.value_map = value_map + self.quote = quote def visit_Name(self, node): typ = super()._try_find_name(node.id) @@ -176,23 +172,7 @@ class StitchingASTTypedRewriter(ASTTypedRewriter): else: # Try to find this value in the host environment and quote it. if node.id in self.host_environment: - value = self.host_environment[node.id] - if inspect.isfunction(value): - # It's a function. We need to translate the function and insert - # a reference to it. - function_name = self.quote_function(value, node.loc) - return asttyped.NameT(id=function_name, ctx=None, - type=self.globals[function_name], - loc=node.loc) - - else: - # It's just a value. Quote it. - synthesizer = ASTSynthesizer(expanded_from=node.loc, - type_map=self.type_map, - value_map=self.value_map) - node = synthesizer.quote(value) - synthesizer.finalize() - return node + return self.quote(self.host_environment[node.id], node.loc) else: diag = diagnostic.Diagnostic("fatal", "name '{name}' is not bound to anything", {"name":node.id}, @@ -200,9 +180,10 @@ class StitchingASTTypedRewriter(ASTTypedRewriter): self.engine.process(diag) class StitchingInferencer(Inferencer): - def __init__(self, engine, type_map, value_map): + def __init__(self, engine, value_map, quote): super().__init__(engine) - self.type_map, self.value_map = type_map, value_map + self.value_map = value_map + self.quote = quote def visit_AttributeT(self, node): self.generic_visit(node) @@ -239,10 +220,7 @@ class StitchingInferencer(Inferencer): # 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. - synthesizer = ASTSynthesizer(type_map=self.type_map, - value_map=self.value_map) - ast = synthesizer.quote(getattr(object_value, node.attr)) - synthesizer.finalize() + ast = self.quote(getattr(object_value, node.attr), object_loc.expanded_from) def proxy_diagnostic(diag): note = diagnostic.Diagnostic("note", @@ -258,7 +236,6 @@ class StitchingInferencer(Inferencer): proxy_engine.process = proxy_diagnostic Inferencer(engine=proxy_engine).visit(ast) IntMonomorphizer(engine=proxy_engine).visit(ast) - MonomorphismValidator(engine=proxy_engine).visit(ast) if node.attr not in object_type.attributes: # We just figured out what the type should be. Add it. @@ -296,7 +273,8 @@ class Stitcher: def finalize(self): inferencer = StitchingInferencer(engine=self.engine, - type_map=self.type_map, value_map=self.value_map) + value_map=self.value_map, + quote=self._quote) # Iterate inference to fixed point. self.inference_finished = False @@ -350,8 +328,7 @@ class Stitcher: asttyped_rewriter = StitchingASTTypedRewriter( engine=self.engine, prelude=self.prelude, globals=self.globals, host_environment=host_environment, - quote_function=self._quote_function, - type_map=self.type_map, value_map=self.value_map) + quote=self._quote) return asttyped_rewriter.visit(function_node) def _function_loc(self, function): @@ -526,6 +503,24 @@ class Stitcher: return self._quote_foreign_function(function, loc, syscall=None) + def _quote(self, value, loc): + if inspect.isfunction(value): + # It's a function. We need to translate the function and insert + # a reference to it. + function_name = self._quote_function(value, loc) + return asttyped.NameT(id=function_name, ctx=None, + type=self.globals[function_name], + loc=loc) + + else: + # It's just a value. Quote it. + synthesizer = ASTSynthesizer(expanded_from=loc, + type_map=self.type_map, + value_map=self.value_map) + node = synthesizer.quote(value) + synthesizer.finalize() + return node + def stitch_call(self, function, args, kwargs): function_node = self._quote_embedded_function(function) self.typedtree.append(function_node) diff --git a/artiq/compiler/transforms/llvm_ir_generator.py b/artiq/compiler/transforms/llvm_ir_generator.py index 2e47b15b3..e4f4efea4 100644 --- a/artiq/compiler/transforms/llvm_ir_generator.py +++ b/artiq/compiler/transforms/llvm_ir_generator.py @@ -991,6 +991,11 @@ class LLVMIRGenerator: lambda: path() + [attr])) llvalue = ll.Constant.literal_struct(llfields) + llconst = ll.GlobalVariable(self.llmodule, llvalue.type, global_name) + llconst.initializer = llvalue + llconst.linkage = "private" + self.llobject_map[value_id] = llconst + return llconst elif builtins.is_none(typ): assert value is None return ll.Constant.literal_struct([]) @@ -1006,15 +1011,12 @@ class LLVMIRGenerator: elif builtins.is_str(typ): assert isinstance(value, (str, bytes)) return self.llstr_of_str(value) + elif types.is_rpc_function(typ): + return ll.Constant.literal_struct([]) else: + print(typ) assert False - llconst = ll.GlobalVariable(self.llmodule, llvalue.type, global_name) - llconst.initializer = llvalue - llconst.linkage = "private" - self.llobject_map[value_id] = llconst - return llconst - def process_Quote(self, insn): assert self.object_map is not None return self._quote(insn.value, insn.type, lambda: [repr(insn.value)]) From f7c8625f6189bad7dec32cee4034824556638728 Mon Sep 17 00:00:00 2001 From: whitequark Date: Thu, 27 Aug 2015 05:56:46 -0500 Subject: [PATCH 238/369] compiler.embedding: support calling methods via RPC as well. --- artiq/compiler/embedding.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/artiq/compiler/embedding.py b/artiq/compiler/embedding.py index 424468bc1..cfeff698f 100644 --- a/artiq/compiler/embedding.py +++ b/artiq/compiler/embedding.py @@ -504,7 +504,7 @@ class Stitcher: syscall=None) def _quote(self, value, loc): - if inspect.isfunction(value): + if inspect.isfunction(value) or inspect.ismethod(value): # It's a function. We need to translate the function and insert # a reference to it. function_name = self._quote_function(value, loc) From 71ebe1778d9a73f304a12a97c84dc3c385a084a8 Mon Sep 17 00:00:00 2001 From: whitequark Date: Thu, 27 Aug 2015 15:40:15 -0500 Subject: [PATCH 239/369] LLVMIRGenerator: remove debug print. --- artiq/compiler/transforms/llvm_ir_generator.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/artiq/compiler/transforms/llvm_ir_generator.py b/artiq/compiler/transforms/llvm_ir_generator.py index e4f4efea4..140a0351a 100644 --- a/artiq/compiler/transforms/llvm_ir_generator.py +++ b/artiq/compiler/transforms/llvm_ir_generator.py @@ -942,8 +942,6 @@ class LLVMIRGenerator: llstackptr = self.llbuilder.call(self.llbuiltin("llvm.stacksave"), []) llresultslot = self.llbuilder.alloca(llfun.type.pointee.args[0].pointee) - print(llfun) - print(llresultslot) self.llbuilder.call(llfun, [llresultslot] + llargs) llresult = self.llbuilder.load(llresultslot) From 84e32db622892b236ba62ed7b6de29bb3b0653ec Mon Sep 17 00:00:00 2001 From: whitequark Date: Thu, 27 Aug 2015 15:40:46 -0500 Subject: [PATCH 240/369] LLVMIRGenerator: handle self-referential class types. --- artiq/compiler/transforms/llvm_ir_generator.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/artiq/compiler/transforms/llvm_ir_generator.py b/artiq/compiler/transforms/llvm_ir_generator.py index 140a0351a..5fe65363b 100644 --- a/artiq/compiler/transforms/llvm_ir_generator.py +++ b/artiq/compiler/transforms/llvm_ir_generator.py @@ -270,6 +270,9 @@ class LLVMIRGenerator: 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()] From a3284f8978231379f92040f43213655aefa5261a Mon Sep 17 00:00:00 2001 From: whitequark Date: Thu, 27 Aug 2015 15:46:36 -0500 Subject: [PATCH 241/369] compiler.types: fix module paths in __repr__. --- artiq/compiler/types.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/artiq/compiler/types.py b/artiq/compiler/types.py index 627fd4acb..1a9856478 100644 --- a/artiq/compiler/types.py +++ b/artiq/compiler/types.py @@ -78,7 +78,7 @@ class TVar(Type): def __repr__(self): if self.parent is self: - return "" % id(self) + return "" % id(self) else: return repr(self.find()) @@ -121,7 +121,7 @@ class TMono(Type): return fn(accum, self) def __repr__(self): - return "py2llvm.types.TMono(%s, %s)" % (repr(self.name), repr(self.params)) + return "artiq.compiler.types.TMono(%s, %s)" % (repr(self.name), repr(self.params)) def __getitem__(self, param): return self.params[param] @@ -167,7 +167,7 @@ class TTuple(Type): return fn(accum, self) def __repr__(self): - return "py2llvm.types.TTuple(%s)" % repr(self.elts) + return "artiq.compiler.types.TTuple(%s)" % repr(self.elts) def __eq__(self, other): return isinstance(other, TTuple) and \ @@ -231,7 +231,7 @@ class TFunction(Type): return fn(accum, self) def __repr__(self): - return "py2llvm.types.TFunction({}, {}, {})".format( + return "artiq.compiler.types.TFunction({}, {}, {})".format( repr(self.args), repr(self.optargs), repr(self.ret)) def __eq__(self, other): @@ -311,7 +311,7 @@ class TBuiltin(Type): return fn(accum, self) def __repr__(self): - return "py2llvm.types.{}({})".format(type(self).__name__, repr(self.name)) + return "artiq.compiler.types.{}({})".format(type(self).__name__, repr(self.name)) def __eq__(self, other): return isinstance(other, TBuiltin) and \ @@ -364,7 +364,7 @@ class TInstance(TMono): self.attributes = attributes def __repr__(self): - return "py2llvm.types.TInstance({}, {})".format( + return "artiq.compiler.types.TInstance({}, {})".format( repr(self.name), repr(self.attributes)) class TMethod(TMono): @@ -401,7 +401,7 @@ class TValue(Type): return fn(accum, self) def __repr__(self): - return "py2llvm.types.TValue(%s)" % repr(self.value) + return "artiq.compiler.types.TValue(%s)" % repr(self.value) def __eq__(self, other): return isinstance(other, TValue) and \ From 9791cbba4dac9eb6b6c8bbd9531ba799bc3bb6f0 Mon Sep 17 00:00:00 2001 From: whitequark Date: Thu, 27 Aug 2015 17:04:28 -0500 Subject: [PATCH 242/369] compiler.embedding: use typedtree hash to iterate inference to fixpoint. --- artiq/compiler/embedding.py | 33 ++++++++++++++++++++++++++++----- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/artiq/compiler/embedding.py b/artiq/compiler/embedding.py index cfeff698f..33cf2b9d0 100644 --- a/artiq/compiler/embedding.py +++ b/artiq/compiler/embedding.py @@ -8,7 +8,7 @@ annotated as ``@kernel`` when they are referenced. import os, re, linecache, inspect, textwrap from collections import OrderedDict, defaultdict -from pythonparser import ast, source, diagnostic, parse_buffer +from pythonparser import ast, algorithm, source, diagnostic, parse_buffer from . import types, builtins, asttyped, prelude from .transforms import ASTTypedRewriter, Inferencer, IntMonomorphizer @@ -101,6 +101,7 @@ class ASTSynthesizer: constructor_type = types.TConstructor(instance_type) constructor_type.attributes['__objectid__'] = builtins.TInt(types.TValue(32)) + instance_type.constructor = constructor_type self.type_map[typ] = instance_type, constructor_type @@ -253,6 +254,24 @@ class StitchingInferencer(Inferencer): super().visit_AttributeT(node) +class TypedtreeHasher(algorithm.Visitor): + def generic_visit(self, node): + def freeze(obj): + if isinstance(obj, ast.AST): + return self.visit(obj) + elif isinstance(obj, types.Type): + return hash(obj.find()) + elif isinstance(obj, list): + return tuple(obj) + else: + assert obj is None or isinstance(obj, (bool, int, float, str)) + return obj + + 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, engine=None): if engine is None: @@ -275,12 +294,17 @@ class Stitcher: inferencer = StitchingInferencer(engine=self.engine, value_map=self.value_map, quote=self._quote) + hasher = TypedtreeHasher() # Iterate inference to fixed point. - self.inference_finished = False - while not self.inference_finished: - self.inference_finished = True + old_typedtree_hash = None + while True: inferencer.visit(self.typedtree) + typedtree_hash = hasher.visit(self.typedtree) + + if old_typedtree_hash == typedtree_hash: + break + old_typedtree_hash = typedtree_hash # After we have found all functions, synthesize a module to hold them. source_buffer = source.Buffer("", "") @@ -488,7 +512,6 @@ class Stitcher: # the final call. function_node = self._quote_embedded_function(function) self.typedtree.insert(0, function_node) - self.inference_finished = False return function_node.name elif function.artiq_embedded.syscall is not None: # Insert a storage-less global whose type instructs the compiler From d0fd61866fc296846c3e360a8f800a573d1d3912 Mon Sep 17 00:00:00 2001 From: whitequark Date: Thu, 27 Aug 2015 17:25:01 -0500 Subject: [PATCH 243/369] compiler.types: print fields of instance types. --- artiq/compiler/types.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/artiq/compiler/types.py b/artiq/compiler/types.py index 1a9856478..d4cb47aef 100644 --- a/artiq/compiler/types.py +++ b/artiq/compiler/types.py @@ -512,6 +512,7 @@ class TypePrinter(object): def __init__(self): self.gen = genalnum() self.map = {} + self.recurse_guard = set() def name(self, typ): typ = typ.find() @@ -519,6 +520,14 @@ class TypePrinter(object): if typ not in self.map: self.map[typ] = "'%s" % next(self.gen) return self.map[typ] + elif isinstance(typ, TInstance): + if typ.name in self.recurse_guard: + return "".format(typ.name) + else: + self.recurse_guard.add(typ.name) + attrs = ", ".join(["{}: {}".format(attr, self.name(typ.attributes[attr])) + for attr in typ.attributes]) + return "".format(typ.name, attrs) elif isinstance(typ, TMono): if typ.params == {}: return typ.name @@ -545,9 +554,13 @@ class TypePrinter(object): elif isinstance(typ, TBuiltinFunction): return "".format(typ.name) elif isinstance(typ, (TConstructor, TExceptionConstructor)): - attrs = ", ".join(["{}: {}".format(attr, self.name(typ.attributes[attr])) - for attr in typ.attributes]) - return "".format(typ.name, attrs) + if typ.name in self.recurse_guard: + return "".format(typ.name) + else: + self.recurse_guard.add(typ.name) + attrs = ", ".join(["{}: {}".format(attr, self.name(typ.attributes[attr])) + for attr in typ.attributes]) + return "".format(typ.name, attrs) elif isinstance(typ, TValue): return repr(typ.value) else: From c21387dc0926553174461fec76ea957eefbb8779 Mon Sep 17 00:00:00 2001 From: whitequark Date: Thu, 27 Aug 2015 19:46:50 -0500 Subject: [PATCH 244/369] compiler.embedding: support calling methods marked as @kernel. --- artiq/compiler/embedding.py | 160 +++++++++++++----- artiq/compiler/transforms/inferencer.py | 6 +- .../compiler/transforms/llvm_ir_generator.py | 10 +- 3 files changed, 125 insertions(+), 51 deletions(-) diff --git a/artiq/compiler/embedding.py b/artiq/compiler/embedding.py index 33cf2b9d0..c93a49f7b 100644 --- a/artiq/compiler/embedding.py +++ b/artiq/compiler/embedding.py @@ -34,10 +34,11 @@ class ObjectMap: return self.forward_map[obj_key] class ASTSynthesizer: - def __init__(self, type_map, value_map, expanded_from=None): + def __init__(self, type_map, value_map, quote_function=None, expanded_from=None): self.source = "" self.source_buffer = source.Buffer(self.source, "") self.type_map, self.value_map = type_map, value_map + self.quote_function = quote_function self.expanded_from = expanded_from def finalize(self): @@ -82,6 +83,10 @@ class ASTSynthesizer: 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 inspect.isfunction(value) or inspect.ismethod(value): + function_name, function_type = self.quote_function(value, self.expanded_from) + return asttyped.NameT(id=function_name, ctx=None, type=function_type, + loc=self._add(repr(value))) else: quote_loc = self._add('`') repr_loc = self._add(repr(value)) @@ -155,6 +160,36 @@ class ASTSynthesizer: begin_loc=begin_loc, end_loc=end_loc, star_loc=None, dstar_loc=None, loc=name_loc.join(end_loc)) + def assign_local(self, var_name, value): + name_loc = self._add(var_name) + _ = self._add(" ") + equals_loc = self._add("=") + _ = self._add(" ") + value_node = self.quote(value) + + var_node = asttyped.NameT(id=var_name, ctx=None, type=value_node.type, + loc=name_loc) + + return ast.Assign(targets=[var_node], value=value_node, + op_locs=[equals_loc], loc=name_loc.join(value_node.loc)) + + def assign_attribute(self, obj, attr_name, value): + obj_node = self.quote(obj) + dot_loc = self._add(".") + name_loc = self._add(attr_name) + _ = self._add(" ") + equals_loc = self._add("=") + _ = self._add(" ") + value_node = self.quote(value) + + attr_node = asttyped.AttributeT(value=obj_node, attr=attr_name, ctx=None, + type=value_node.type, + dot_loc=dot_loc, attr_loc=name_loc, + loc=obj_node.loc.join(name_loc)) + + return ast.Assign(targets=[attr_node], value=value_node, + op_locs=[equals_loc], loc=name_loc.join(value_node.loc)) + class StitchingASTTypedRewriter(ASTTypedRewriter): def __init__(self, engine, prelude, globals, host_environment, quote): super().__init__(engine, prelude) @@ -221,7 +256,20 @@ class StitchingInferencer(Inferencer): # 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. - ast = self.quote(getattr(object_value, node.attr), object_loc.expanded_from) + attr_value = getattr(object_value, node.attr) + if (inspect.ismethod(attr_value) and hasattr(attr_value.__func__, 'artiq_embedded') + and types.is_instance(object_type)): + # 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 = attr_value.__func__ + else: + attributes = object_type.attributes + + ast = self.quote(attr_value, None) def proxy_diagnostic(diag): note = diagnostic.Diagnostic("note", @@ -238,17 +286,17 @@ class StitchingInferencer(Inferencer): Inferencer(engine=proxy_engine).visit(ast) IntMonomorphizer(engine=proxy_engine).visit(ast) - if node.attr not in object_type.attributes: + if node.attr not in attributes: # We just figured out what the type should be. Add it. - object_type.attributes[node.attr] = ast.type - elif object_type.attributes[node.attr] != ast.type: + attributes[node.attr] = ast.type + elif attributes[node.attr] != ast.type: # Does this conflict with an earlier guess? printer = types.TypePrinter() diag = diagnostic.Diagnostic("error", "host object has an attribute of type {typea}, which is" " different from previously inferred type {typeb}", {"typea": printer.name(ast.type), - "typeb": printer.name(object_type.attributes[node.attr])}, + "typeb": printer.name(attributes[node.attr])}, object_loc) self.engine.process(diag) @@ -261,11 +309,9 @@ class TypedtreeHasher(algorithm.Visitor): return self.visit(obj) elif isinstance(obj, types.Type): return hash(obj.find()) - elif isinstance(obj, list): - return tuple(obj) else: - assert obj is None or isinstance(obj, (bool, int, float, str)) - return obj + # We don't care; only types change during inference. + pass fields = node._fields if hasattr(node, '_types'): @@ -281,6 +327,7 @@ class Stitcher: self.name = "" self.typedtree = [] + self.inject_at = 0 self.prelude = prelude.globals() self.globals = {} @@ -290,6 +337,17 @@ class Stitcher: self.type_map = {} self.value_map = defaultdict(lambda: []) + def stitch_call(self, function, args, kwargs): + function_node = self._quote_embedded_function(function) + self.typedtree.append(function_node) + + # We synthesize source code for the initial call so that + # diagnostics would have something meaningful to display to the user. + synthesizer = self._synthesizer() + call_node = synthesizer.call(function_node, args, kwargs) + synthesizer.finalize() + self.typedtree.append(call_node) + def finalize(self): inferencer = StitchingInferencer(engine=self.engine, value_map=self.value_map, @@ -306,12 +364,50 @@ class Stitcher: break old_typedtree_hash = typedtree_hash + # For every host class we embed, add an appropriate constructor + # as a global. This is necessary for method lookup, which uses + # the getconstructor instruction. + for instance_type, constructor_type in list(self.type_map.values()): + # Do we have any direct reference to a constructor? + if len(self.value_map[constructor_type]) > 0: + # Yes, use it. + constructor, _constructor_loc = self.value_map[constructor_type][0] + else: + # No, extract one from a reference to an instance. + instance, _instance_loc = self.value_map[instance_type][0] + constructor = type(instance) + + self.globals[constructor_type.name] = constructor_type + + synthesizer = self._synthesizer() + ast = synthesizer.assign_local(constructor_type.name, constructor) + synthesizer.finalize() + self._inject(ast) + + for attr in constructor_type.attributes: + if types.is_function(constructor_type.attributes[attr]): + synthesizer = self._synthesizer() + ast = synthesizer.assign_attribute(constructor, attr, + getattr(constructor, attr)) + synthesizer.finalize() + self._inject(ast) + # 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, + type_map=self.type_map, + value_map=self.value_map, + quote_function=self._quote_function) + def _quote_embedded_function(self, function): if not hasattr(function, "artiq_embedded"): raise ValueError("{} is not an embedded function".format(repr(function))) @@ -414,10 +510,7 @@ class Stitcher: # 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. - synthesizer = ASTSynthesizer(type_map=self.type_map, - value_map=self.value_map) - ast = synthesizer.quote(param.default) - synthesizer.finalize() + ast = self._quote(param.default, None) def proxy_diagnostic(diag): note = diagnostic.Diagnostic("note", @@ -499,11 +592,12 @@ class Stitcher: self.globals[function_name] = function_type self.functions[function] = function_name - return function_name + return function_name, function_type def _quote_function(self, function, loc): if function in self.functions: - return self.functions[function] + function_name = self.functions[function] + return function_name, self.globals[function_name] if hasattr(function, "artiq_embedded"): if function.artiq_embedded.function is not None: @@ -511,8 +605,8 @@ class Stitcher: # It doesn't really matter where we insert as long as it is before # the final call. function_node = self._quote_embedded_function(function) - self.typedtree.insert(0, function_node) - return function_node.name + self._inject(function_node) + return function_node.name, self.globals[function_node.name] elif 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. @@ -527,31 +621,7 @@ class Stitcher: syscall=None) def _quote(self, value, loc): - if inspect.isfunction(value) or inspect.ismethod(value): - # It's a function. We need to translate the function and insert - # a reference to it. - function_name = self._quote_function(value, loc) - return asttyped.NameT(id=function_name, ctx=None, - type=self.globals[function_name], - loc=loc) - - else: - # It's just a value. Quote it. - synthesizer = ASTSynthesizer(expanded_from=loc, - type_map=self.type_map, - value_map=self.value_map) - node = synthesizer.quote(value) - synthesizer.finalize() - return node - - def stitch_call(self, function, args, kwargs): - function_node = self._quote_embedded_function(function) - self.typedtree.append(function_node) - - # We synthesize source code for the initial call so that - # diagnostics would have something meaningful to display to the user. - synthesizer = ASTSynthesizer(type_map=self.type_map, - value_map=self.value_map) - call_node = synthesizer.call(function_node, args, kwargs) + synthesizer = self._synthesizer(loc) + node = synthesizer.quote(value) synthesizer.finalize() - self.typedtree.append(call_node) + return node diff --git a/artiq/compiler/transforms/inferencer.py b/artiq/compiler/transforms/inferencer.py index 119ffdab5..aa726e98f 100644 --- a/artiq/compiler/transforms/inferencer.py +++ b/artiq/compiler/transforms/inferencer.py @@ -127,8 +127,10 @@ class Inferencer(algorithm.Visitor): when=" while inferring the type for self argument") attr_type = types.TMethod(object_type, attr_type) - self._unify(node.type, attr_type, - node.loc, None) + + if not types.is_var(attr_type): + self._unify(node.type, attr_type, + node.loc, None) else: if node.attr_loc.source_buffer == node.value.loc.source_buffer: highlights, notes = [node.value.loc], [] diff --git a/artiq/compiler/transforms/llvm_ir_generator.py b/artiq/compiler/transforms/llvm_ir_generator.py index 5fe65363b..51218b294 100644 --- a/artiq/compiler/transforms/llvm_ir_generator.py +++ b/artiq/compiler/transforms/llvm_ir_generator.py @@ -266,7 +266,7 @@ class LLVMIRGenerator: elif types.is_constructor(typ): name = "class.{}".format(typ.name) else: - name = typ.name + name = "instance.{}".format(typ.name) llty = self.llcontext.get_identified_type(name) if llty.elements is None: @@ -991,7 +991,7 @@ class LLVMIRGenerator: llfields.append(self._quote(getattr(value, attr), typ.attributes[attr], lambda: path() + [attr])) - llvalue = ll.Constant.literal_struct(llfields) + llvalue = ll.Constant(llty.pointee, llfields) llconst = ll.GlobalVariable(self.llmodule, llvalue.type, global_name) llconst.initializer = llvalue llconst.linkage = "private" @@ -1012,8 +1012,10 @@ class LLVMIRGenerator: elif builtins.is_str(typ): assert isinstance(value, (str, bytes)) return self.llstr_of_str(value) - elif types.is_rpc_function(typ): - return ll.Constant.literal_struct([]) + elif types.is_function(typ): + # RPC and C functions have no runtime representation; ARTIQ + # functions are initialized explicitly. + return ll.Constant(llty, ll.Undefined) else: print(typ) assert False From d80be482fcc802a53c4324a41c5b984c8b1d80c7 Mon Sep 17 00:00:00 2001 From: whitequark Date: Fri, 28 Aug 2015 00:37:46 -0500 Subject: [PATCH 245/369] Allow clearing core device log buffer. This is useful to get a fresh environment, such as when running tests that print to log buffer. --- artiq/coredevice/comm_generic.py | 28 ++++++++++++++++++---------- soc/runtime/log.c | 5 +++++ soc/runtime/log.h | 1 + soc/runtime/session.c | 8 ++++++++ 4 files changed, 32 insertions(+), 10 deletions(-) diff --git a/artiq/coredevice/comm_generic.py b/artiq/coredevice/comm_generic.py index b1967b352..a42eeb501 100644 --- a/artiq/coredevice/comm_generic.py +++ b/artiq/coredevice/comm_generic.py @@ -12,23 +12,26 @@ logger = logging.getLogger(__name__) class _H2DMsgType(Enum): LOG_REQUEST = 1 - IDENT_REQUEST = 2 - SWITCH_CLOCK = 3 + LOG_CLEAR = 2 - LOAD_LIBRARY = 4 - RUN_KERNEL = 5 + IDENT_REQUEST = 3 + SWITCH_CLOCK = 4 - RPC_REPLY = 6 - RPC_EXCEPTION = 7 + LOAD_LIBRARY = 5 + RUN_KERNEL = 6 - FLASH_READ_REQUEST = 8 - FLASH_WRITE_REQUEST = 9 - FLASH_ERASE_REQUEST = 10 - FLASH_REMOVE_REQUEST = 11 + RPC_REPLY = 7 + RPC_EXCEPTION = 8 + + FLASH_READ_REQUEST = 9 + FLASH_WRITE_REQUEST = 10 + FLASH_ERASE_REQUEST = 11 + FLASH_REMOVE_REQUEST = 12 class _D2HMsgType(Enum): LOG_REPLY = 1 + IDENT_REPLY = 2 CLOCK_SWITCH_COMPLETED = 3 CLOCK_SWITCH_FAILED = 4 @@ -235,6 +238,11 @@ class CommGeneric: self._read_expect(_D2HMsgType.LOG_REPLY) return self._read_chunk(self._read_length).decode('utf-8') + def clear_log(self): + self._write_empty(_H2DMsgType.LOG_CLEAR) + + self._read_empty(_D2HMsgType.LOG_REPLY) + def flash_storage_read(self, key): self._write_header(_H2DMsgType.FLASH_READ_REQUEST) self._write_string(key) diff --git a/soc/runtime/log.c b/soc/runtime/log.c index ebc7990ec..413857564 100644 --- a/soc/runtime/log.c +++ b/soc/runtime/log.c @@ -61,3 +61,8 @@ void log_get(char *outbuf) j = (j + 1) % LOG_BUFFER_SIZE; } } + +void log_clear() +{ + memset(buffer, 0, sizeof(buffer)); +} diff --git a/soc/runtime/log.h b/soc/runtime/log.h index 0fb7ce8b1..74e3e815e 100644 --- a/soc/runtime/log.h +++ b/soc/runtime/log.h @@ -12,5 +12,6 @@ void log_va(const char *fmt, va_list args); void log(const char *fmt, ...); void log_get(char *outbuf); +void log_clear(); #endif /* __LOG_H */ diff --git a/soc/runtime/session.c b/soc/runtime/session.c index ff6629f4f..3bdf3bb81 100644 --- a/soc/runtime/session.c +++ b/soc/runtime/session.c @@ -310,6 +310,8 @@ void session_end(void) /* host to device */ enum { REMOTEMSG_TYPE_LOG_REQUEST = 1, + REMOTEMSG_TYPE_LOG_CLEAR, + REMOTEMSG_TYPE_IDENT_REQUEST, REMOTEMSG_TYPE_SWITCH_CLOCK, @@ -328,6 +330,7 @@ enum { /* device to host */ enum { REMOTEMSG_TYPE_LOG_REPLY = 1, + REMOTEMSG_TYPE_IDENT_REPLY, REMOTEMSG_TYPE_CLOCK_SWITCH_COMPLETED, REMOTEMSG_TYPE_CLOCK_SWITCH_FAILED, @@ -383,6 +386,11 @@ static int process_input(void) out_packet_finish(); break; + case REMOTEMSG_TYPE_LOG_CLEAR: + log_clear(); + out_packet_empty(REMOTEMSG_TYPE_LOG_REPLY); + break; + case REMOTEMSG_TYPE_FLASH_READ_REQUEST: { #if SPIFLASH_SECTOR_SIZE - 4 > BUFFER_OUT_SIZE - 9 #error Output buffer cannot hold the flash storage data From 7c1abb25ec7c02d3d161963262dcac3d8ec46812 Mon Sep 17 00:00:00 2001 From: whitequark Date: Fri, 28 Aug 2015 00:47:28 -0500 Subject: [PATCH 246/369] compiler.embedding: test all diagnostics. Also, unify and improve diagnostic messages. --- artiq/compiler/embedding.py | 54 +++++++++---------- artiq/compiler/testbench/embedding.py | 25 +++++++++ artiq/coredevice/comm_dummy.py | 24 +++------ artiq/coredevice/core.py | 1 - lit-test/test/embedding/ddb.pyon | 8 +++ lit-test/test/embedding/error_attr_absent.py | 16 ++++++ .../test/embedding/error_attr_conflict.py | 21 ++++++++ lit-test/test/embedding/error_attr_unify.py | 17 ++++++ .../test/embedding/error_rpc_annot_return.py | 14 +++++ .../test/embedding/error_rpc_default_unify.py | 15 ++++++ lit-test/test/embedding/error_rpc_return.py | 14 +++++ .../test/embedding/error_syscall_annot.py | 15 ++++++ .../embedding/error_syscall_annot_return.py | 15 ++++++ lit-test/test/embedding/error_syscall_arg.py | 15 ++++++ .../embedding/error_syscall_default_arg.py | 14 +++++ .../test/embedding/error_syscall_return.py | 14 +++++ 16 files changed, 236 insertions(+), 46 deletions(-) create mode 100644 artiq/compiler/testbench/embedding.py create mode 100644 lit-test/test/embedding/ddb.pyon create mode 100644 lit-test/test/embedding/error_attr_absent.py create mode 100644 lit-test/test/embedding/error_attr_conflict.py create mode 100644 lit-test/test/embedding/error_attr_unify.py create mode 100644 lit-test/test/embedding/error_rpc_annot_return.py create mode 100644 lit-test/test/embedding/error_rpc_default_unify.py create mode 100644 lit-test/test/embedding/error_rpc_return.py create mode 100644 lit-test/test/embedding/error_syscall_annot.py create mode 100644 lit-test/test/embedding/error_syscall_annot_return.py create mode 100644 lit-test/test/embedding/error_syscall_arg.py create mode 100644 lit-test/test/embedding/error_syscall_default_arg.py create mode 100644 lit-test/test/embedding/error_syscall_return.py diff --git a/artiq/compiler/embedding.py b/artiq/compiler/embedding.py index c93a49f7b..6910f185e 100644 --- a/artiq/compiler/embedding.py +++ b/artiq/compiler/embedding.py @@ -269,12 +269,11 @@ class StitchingInferencer(Inferencer): else: attributes = object_type.attributes - ast = self.quote(attr_value, None) + ast = self.quote(attr_value, object_loc.expanded_from) def proxy_diagnostic(diag): note = diagnostic.Diagnostic("note", - "expanded from here while trying to infer a type for an" - " attribute '{attr}' of a host object", + "while inferring a type for an attribute '{attr}' of a host object", {"attr": node.attr}, node.loc) diag.notes.append(note) @@ -293,10 +292,11 @@ class StitchingInferencer(Inferencer): # Does this conflict with an earlier guess? printer = types.TypePrinter() diag = diagnostic.Diagnostic("error", - "host object has an attribute of type {typea}, which is" - " different from previously inferred type {typeb}", + "host object has an attribute '{attr}' of type {typea}, which is" + " different from previously inferred type {typeb} for the same attribute", {"typea": printer.name(ast.type), - "typeb": printer.name(attributes[node.attr])}, + "typeb": printer.name(attributes[node.attr]), + "attr": node.attr}, object_loc) self.engine.process(diag) @@ -465,27 +465,23 @@ class Stitcher: source_buffer = source.Buffer(source_line, filename, line) return source.Range(source_buffer, column, column) - def _function_def_note(self, function): - return diagnostic.Diagnostic("note", - "definition of function '{function}'", - {"function": function.__name__}, - self._function_loc(function)) + def _call_site_note(self, call_loc, is_syscall): + if is_syscall: + return diagnostic.Diagnostic("note", + "in system call here", {}, + call_loc) + else: + return diagnostic.Diagnostic("note", + "in function called remotely here", {}, + call_loc) def _extract_annot(self, function, annot, kind, call_loc, is_syscall): if not isinstance(annot, types.Type): - if is_syscall: - note = diagnostic.Diagnostic("note", - "in system call here", {}, - call_loc) - else: - note = diagnostic.Diagnostic("note", - "in function called remotely here", {}, - call_loc) diag = diagnostic.Diagnostic("error", "type annotation for {kind}, '{annot}', is not an ARTIQ type", {"kind": kind, "annot": repr(annot)}, self._function_loc(function), - notes=[note]) + notes=[self._call_site_note(call_loc, is_syscall)]) self.engine.process(diag) return types.TVar() @@ -503,7 +499,8 @@ class Stitcher: diag = diagnostic.Diagnostic("error", "system call argument '{argument}' must have a type annotation", {"argument": param.name}, - self._function_loc(function)) + self._function_loc(function), + notes=[self._call_site_note(loc, is_syscall)]) self.engine.process(diag) elif param.default is not inspect.Parameter.empty: # Try and infer the type from the default value. @@ -517,10 +514,10 @@ class Stitcher: "expanded from here while trying to infer a type for an" " unannotated optional argument '{argument}' from its default value", {"argument": param.name}, - loc) + self._function_loc(function)) diag.notes.append(note) - diag.notes.append(self._function_def_note(function)) + diag.notes.append(self._call_site_note(loc, is_syscall)) self.engine.process(diag) @@ -556,12 +553,13 @@ class Stitcher: is_syscall=syscall is not None) elif syscall is None: optarg_types[param.name] = self._type_of_param(function, loc, param, - is_syscall=syscall is not None) + is_syscall=False) else: diag = diagnostic.Diagnostic("error", "system call argument '{argument}' must not have a default value", {"argument": param.name}, - self._function_loc(function)) + self._function_loc(function), + notes=[self._call_site_note(loc, is_syscall=True)]) self.engine.process(diag) if signature.return_annotation is not inspect.Signature.empty: @@ -570,13 +568,15 @@ class Stitcher: elif syscall is None: diag = diagnostic.Diagnostic("error", "function must have a return type annotation to be called remotely", {}, - self._function_loc(function)) + self._function_loc(function), + notes=[self._call_site_note(loc, is_syscall=False)]) self.engine.process(diag) ret_type = types.TVar() else: # syscall is not None diag = diagnostic.Diagnostic("error", "system call must have a return type annotation", {}, - self._function_loc(function)) + self._function_loc(function), + notes=[self._call_site_note(loc, is_syscall=True)]) self.engine.process(diag) ret_type = types.TVar() diff --git a/artiq/compiler/testbench/embedding.py b/artiq/compiler/testbench/embedding.py new file mode 100644 index 000000000..432638f19 --- /dev/null +++ b/artiq/compiler/testbench/embedding.py @@ -0,0 +1,25 @@ +import sys, os + +from artiq.protocols.file_db import FlatFileDB +from artiq.master.worker_db import DeviceManager + +from artiq.coredevice.core import Core, CompileError + +def main(): + with open(sys.argv[1]) as f: + testcase_code = compile(f.read(), f.name, "exec") + testcase_vars = {} + exec(testcase_code, testcase_vars) + + ddb_path = os.path.join(os.path.dirname(sys.argv[1]), "ddb.pyon") + + try: + core = Core(dmgr=DeviceManager(FlatFileDB(ddb_path))) + core.run(testcase_vars["entrypoint"], (), {}) + print(core.comm.get_log()) + core.comm.clear_log() + except CompileError as error: + print("\n".join(error.__cause__.diagnostic.render(only_line=True))) + +if __name__ == "__main__": + main() diff --git a/artiq/coredevice/comm_dummy.py b/artiq/coredevice/comm_dummy.py index 072c9bdd8..82a1a9575 100644 --- a/artiq/coredevice/comm_dummy.py +++ b/artiq/coredevice/comm_dummy.py @@ -8,23 +8,11 @@ class Comm: def switch_clock(self, external): pass - def load(self, kcode): - print("================") - print(" LLVM IR") - print("================") - print(kcode) + def load(self, kernel_library): + pass - def run(self, kname): - print("RUN: "+kname) + def run(self): + pass - def serve(self, rpc_map, exception_map): - print("================") - print(" RPC map") - print("================") - for k, v in sorted(rpc_map.items(), key=itemgetter(0)): - print(str(k)+" -> "+str(v)) - print("================") - print(" Exception map") - print("================") - for k, v in sorted(exception_map.items(), key=itemgetter(0)): - print(str(k)+" -> "+str(v)) + def serve(self, object_map, symbolizer): + pass diff --git a/artiq/coredevice/core.py b/artiq/coredevice/core.py index e02b1ca47..18cc981c3 100644 --- a/artiq/coredevice/core.py +++ b/artiq/coredevice/core.py @@ -48,7 +48,6 @@ class Core: return stitcher.object_map, stripped_library, \ lambda addresses: target.symbolize(library, addresses) except diagnostic.Error as error: - print("\n".join(error.diagnostic.render(colored=True)), file=sys.stderr) raise CompileError() from error def run(self, function, args, kwargs): diff --git a/lit-test/test/embedding/ddb.pyon b/lit-test/test/embedding/ddb.pyon new file mode 100644 index 000000000..7c1bb62ef --- /dev/null +++ b/lit-test/test/embedding/ddb.pyon @@ -0,0 +1,8 @@ +{ + "comm": { + "type": "local", + "module": "artiq.coredevice.comm_dummy", + "class": "Comm", + "arguments": {} + } +} diff --git a/lit-test/test/embedding/error_attr_absent.py b/lit-test/test/embedding/error_attr_absent.py new file mode 100644 index 000000000..296ed860a --- /dev/null +++ b/lit-test/test/embedding/error_attr_absent.py @@ -0,0 +1,16 @@ +# RUN: %python -m artiq.compiler.testbench.embedding %s >%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/lit-test/test/embedding/error_attr_conflict.py b/lit-test/test/embedding/error_attr_conflict.py new file mode 100644 index 000000000..a5c960d5c --- /dev/null +++ b/lit-test/test/embedding/error_attr_conflict.py @@ -0,0 +1,21 @@ +# RUN: %python -m artiq.compiler.testbench.embedding %s >%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 int(width=32) for the same attribute + i1.x + # CHECK-L: ${LINE:+1}: note: expanded from here + i2.x diff --git a/lit-test/test/embedding/error_attr_unify.py b/lit-test/test/embedding/error_attr_unify.py new file mode 100644 index 000000000..e891ec73b --- /dev/null +++ b/lit-test/test/embedding/error_attr_unify.py @@ -0,0 +1,17 @@ +# RUN: %python -m artiq.compiler.testbench.embedding %s >%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 int(width='a) 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/lit-test/test/embedding/error_rpc_annot_return.py b/lit-test/test/embedding/error_rpc_annot_return.py new file mode 100644 index 000000000..063ac6c28 --- /dev/null +++ b/lit-test/test/embedding/error_rpc_annot_return.py @@ -0,0 +1,14 @@ +# RUN: %python -m artiq.compiler.testbench.embedding %s >%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/lit-test/test/embedding/error_rpc_default_unify.py b/lit-test/test/embedding/error_rpc_default_unify.py new file mode 100644 index 000000000..9cb483628 --- /dev/null +++ b/lit-test/test/embedding/error_rpc_default_unify.py @@ -0,0 +1,15 @@ +# RUN: %python -m artiq.compiler.testbench.embedding %s >%t +# RUN: OutputCheck %s --file-to-check=%t + +from artiq.language.core import * +from artiq.language.types import * + +# CHECK-L: :1: error: cannot unify int(width='a) with str +# CHECK-L: ${LINE:+1}: note: expanded from here while trying to infer a type for an unannotated optional argument 'x' from its default value +def foo(x=[1,"x"]): + pass + +@kernel +def entrypoint(): + # CHECK-L: ${LINE:+1}: note: in function called remotely here + foo() diff --git a/lit-test/test/embedding/error_rpc_return.py b/lit-test/test/embedding/error_rpc_return.py new file mode 100644 index 000000000..f3f8ddd70 --- /dev/null +++ b/lit-test/test/embedding/error_rpc_return.py @@ -0,0 +1,14 @@ +# RUN: %python -m artiq.compiler.testbench.embedding %s >%t +# RUN: OutputCheck %s --file-to-check=%t + +from artiq.language.core import * +from artiq.language.types import * + +# CHECK-L: ${LINE:+1}: error: function must have a return type annotation to be called remotely +def foo(): + pass + +@kernel +def entrypoint(): + # CHECK-L: ${LINE:+1}: note: in function called remotely here + foo() diff --git a/lit-test/test/embedding/error_syscall_annot.py b/lit-test/test/embedding/error_syscall_annot.py new file mode 100644 index 000000000..cbb5aab22 --- /dev/null +++ b/lit-test/test/embedding/error_syscall_annot.py @@ -0,0 +1,15 @@ +# RUN: %python -m artiq.compiler.testbench.embedding %s >%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/lit-test/test/embedding/error_syscall_annot_return.py b/lit-test/test/embedding/error_syscall_annot_return.py new file mode 100644 index 000000000..20f1d4ac5 --- /dev/null +++ b/lit-test/test/embedding/error_syscall_annot_return.py @@ -0,0 +1,15 @@ +# RUN: %python -m artiq.compiler.testbench.embedding %s >%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/lit-test/test/embedding/error_syscall_arg.py b/lit-test/test/embedding/error_syscall_arg.py new file mode 100644 index 000000000..b944cc0e9 --- /dev/null +++ b/lit-test/test/embedding/error_syscall_arg.py @@ -0,0 +1,15 @@ +# RUN: %python -m artiq.compiler.testbench.embedding %s >%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/lit-test/test/embedding/error_syscall_default_arg.py b/lit-test/test/embedding/error_syscall_default_arg.py new file mode 100644 index 000000000..df025ff19 --- /dev/null +++ b/lit-test/test/embedding/error_syscall_default_arg.py @@ -0,0 +1,14 @@ +# RUN: %python -m artiq.compiler.testbench.embedding %s >%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/lit-test/test/embedding/error_syscall_return.py b/lit-test/test/embedding/error_syscall_return.py new file mode 100644 index 000000000..c82db063d --- /dev/null +++ b/lit-test/test/embedding/error_syscall_return.py @@ -0,0 +1,14 @@ +# RUN: %python -m artiq.compiler.testbench.embedding %s >%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() From 13e612c11bd9e06c57713b9e82d7bfb74f4348db Mon Sep 17 00:00:00 2001 From: whitequark Date: Fri, 28 Aug 2015 00:51:31 -0500 Subject: [PATCH 247/369] Fix tests. --- artiq/compiler/types.py | 8 ++++---- lit-test/test/inferencer/builtin_calls.py | 8 ++++---- lit-test/test/inferencer/class.py | 4 ++-- lit-test/test/inferencer/error_method.py | 2 +- lit-test/test/inferencer/unify.py | 8 ++++---- 5 files changed, 15 insertions(+), 15 deletions(-) diff --git a/artiq/compiler/types.py b/artiq/compiler/types.py index d4cb47aef..32501d961 100644 --- a/artiq/compiler/types.py +++ b/artiq/compiler/types.py @@ -521,10 +521,10 @@ class TypePrinter(object): self.map[typ] = "'%s" % next(self.gen) return self.map[typ] elif isinstance(typ, TInstance): - if typ.name in self.recurse_guard: + if typ in self.recurse_guard: return "".format(typ.name) else: - self.recurse_guard.add(typ.name) + self.recurse_guard.add(typ) attrs = ", ".join(["{}: {}".format(attr, self.name(typ.attributes[attr])) for attr in typ.attributes]) return "".format(typ.name, attrs) @@ -554,10 +554,10 @@ class TypePrinter(object): elif isinstance(typ, TBuiltinFunction): return "".format(typ.name) elif isinstance(typ, (TConstructor, TExceptionConstructor)): - if typ.name in self.recurse_guard: + if typ in self.recurse_guard: return "".format(typ.name) else: - self.recurse_guard.add(typ.name) + self.recurse_guard.add(typ) attrs = ", ".join(["{}: {}".format(attr, self.name(typ.attributes[attr])) for attr in typ.attributes]) return "".format(typ.name, attrs) diff --git a/lit-test/test/inferencer/builtin_calls.py b/lit-test/test/inferencer/builtin_calls.py index 1ba629fba..61a97fb4a 100644 --- a/lit-test/test/inferencer/builtin_calls.py +++ b/lit-test/test/inferencer/builtin_calls.py @@ -4,22 +4,22 @@ # CHECK-L: bool:():bool bool() -# CHECK-L: bool:([]:list(elt='a)):bool +# CHECK-L: bool:([]:list(elt='a)):bool bool([]) # CHECK-L: int:():int(width='b) int() -# CHECK-L: int:(1.0:float):int(width='c) +# CHECK-L: int:(1.0:float):int(width='c) int(1.0) -# CHECK-L: int:(1.0:float, width=64:int(width='d)):int(width=64) +# CHECK-L: int:(1.0:float, width=64:int(width='d)):int(width=64) int(1.0, width=64) # CHECK-L: float:():float float() -# CHECK-L: float:(1:int(width='e)):float +# CHECK-L: float:(1:int(width='e)):float float(1) # CHECK-L: list:():list(elt='f) diff --git a/lit-test/test/inferencer/class.py b/lit-test/test/inferencer/class.py index 455f3e810..db8ddd27c 100644 --- a/lit-test/test/inferencer/class.py +++ b/lit-test/test/inferencer/class.py @@ -8,12 +8,12 @@ class c: def m(self): pass -# CHECK-L: c:NoneType, m: (self:)->NoneType}> c # CHECK-L: .a:int(width='a) c.a # CHECK-L: .f:()->NoneType c.f -# CHECK-L: .m:method(fn=(self:c)->NoneType, self=c) +# CHECK-L: .m:method(fn=(self:)->NoneType, self=) c().m() diff --git a/lit-test/test/inferencer/error_method.py b/lit-test/test/inferencer/error_method.py index bf9b1fbe8..bf9f87b86 100644 --- a/lit-test/test/inferencer/error_method.py +++ b/lit-test/test/inferencer/error_method.py @@ -12,5 +12,5 @@ class c: c().f() c.g(1) -# CHECK-L: ${LINE:+1}: error: cannot unify c with int(width='a) while inferring the type for self argument +# CHECK-L: ${LINE:+1}: error: cannot unify with int(width='a) while inferring the type for self argument c().g() diff --git a/lit-test/test/inferencer/unify.py b/lit-test/test/inferencer/unify.py index 31679beb6..59d23e937 100644 --- a/lit-test/test/inferencer/unify.py +++ b/lit-test/test/inferencer/unify.py @@ -61,13 +61,13 @@ IndexError() # CHECK-L: IndexError:():IndexError IndexError("x") -# CHECK-L: IndexError:("x":str):IndexError +# CHECK-L: IndexError:("x":str):IndexError IndexError("x", 1) -# CHECK-L: IndexError:("x":str, 1:int(width=64)):IndexError +# CHECK-L: IndexError:("x":str, 1:int(width=64)):IndexError IndexError("x", 1, 1) -# CHECK-L: IndexError:("x":str, 1:int(width=64), 1:int(width=64)):IndexError +# CHECK-L: IndexError:("x":str, 1:int(width=64), 1:int(width=64)):IndexError IndexError("x", 1, 1, 1) -# CHECK-L: IndexError:("x":str, 1:int(width=64), 1:int(width=64), 1:int(width=64)):IndexError +# CHECK-L: IndexError:("x":str, 1:int(width=64), 1:int(width=64), 1:int(width=64)):IndexError From 6b55e3bd80f7bb2510b9a7a892aeb5fa191f5c39 Mon Sep 17 00:00:00 2001 From: whitequark Date: Fri, 28 Aug 2015 01:14:19 -0500 Subject: [PATCH 248/369] compiler.embedding: use qualified name when embedding methods. --- artiq/compiler/embedding.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/artiq/compiler/embedding.py b/artiq/compiler/embedding.py index 6910f185e..141d9eca1 100644 --- a/artiq/compiler/embedding.py +++ b/artiq/compiler/embedding.py @@ -101,7 +101,7 @@ class ASTSynthesizer: if typ in self.type_map: instance_type, constructor_type = self.type_map[typ] else: - instance_type = types.TInstance("{}.{}".format(typ.__module__, typ.__name__)) + instance_type = types.TInstance("{}.{}".format(typ.__module__, typ.__qualname__)) instance_type.attributes['__objectid__'] = builtins.TInt(types.TValue(32)) constructor_type = types.TConstructor(instance_type) From d473d58b418979199bc0ff4b3429944699cb204c Mon Sep 17 00:00:00 2001 From: whitequark Date: Fri, 28 Aug 2015 01:43:46 -0500 Subject: [PATCH 249/369] artiq_{compile,run}: adapt to new compiler. --- artiq/compiler/embedding.py | 4 ++++ artiq/compiler/targets.py | 2 ++ artiq/frontend/artiq_compile.py | 22 ++++++++++------------ artiq/frontend/artiq_coretool.py | 2 +- artiq/frontend/artiq_run.py | 13 +++++++++---- artiq/language/core.py | 2 +- 6 files changed, 27 insertions(+), 18 deletions(-) diff --git a/artiq/compiler/embedding.py b/artiq/compiler/embedding.py index 141d9eca1..199c98f0b 100644 --- a/artiq/compiler/embedding.py +++ b/artiq/compiler/embedding.py @@ -33,6 +33,10 @@ class ObjectMap: def retrieve(self, obj_key): return self.forward_map[obj_key] + def has_rpc(self): + return any(filter(lambda x: inspect.isfunction(x) or inspect.ismethod(x), + self.forward_map.values())) + class ASTSynthesizer: def __init__(self, type_map, value_map, quote_function=None, expanded_from=None): self.source = "" diff --git a/artiq/compiler/targets.py b/artiq/compiler/targets.py index 1ed440236..3d9db3a05 100644 --- a/artiq/compiler/targets.py +++ b/artiq/compiler/targets.py @@ -145,6 +145,8 @@ class Target: backtrace = [] for function_name, location, address in zip(lines[::2], lines[1::2], addresses): filename, line = location.rsplit(":", 1) + if filename == '??': + continue # can't get column out of addr2line D: backtrace.append((filename, int(line), -1, function_name, address)) return backtrace diff --git a/artiq/frontend/artiq_compile.py b/artiq/frontend/artiq_compile.py index c47360c51..9262edcb3 100755 --- a/artiq/frontend/artiq_compile.py +++ b/artiq/frontend/artiq_compile.py @@ -45,29 +45,27 @@ def main(): arguments = parse_arguments(args.arguments) exp_inst = exp(dmgr, pdb, **arguments) - if (not hasattr(exp.run, "k_function_info") - or not exp.run.k_function_info): + if not hasattr(exp.run, "artiq_embedded"): raise ValueError("Experiment entry point must be a kernel") - core_name = exp.run.k_function_info.core_name + core_name = exp.run.artiq_embedded.core_name core = getattr(exp_inst, core_name) - binary, rpc_map, _ = core.compile(exp.run.k_function_info.k_function, - [exp_inst], {}, - with_attr_writeback=False) + object_map, kernel_library, symbolizer = \ + core.compile(exp.run, [exp_inst], {}, + with_attr_writeback=False) finally: dmgr.close_devices() - if rpc_map: + if object_map.has_rpc(): raise ValueError("Experiment must not use RPC") output = args.output if output is None: - output = args.file - if output.endswith(".py"): - output = output[:-3] - output += ".elf" + basename, ext = os.path.splitext(args.file) + output = "{}.elf".format(basename) + with open(output, "wb") as f: - f.write(binary) + f.write(kernel_library) if __name__ == "__main__": main() diff --git a/artiq/frontend/artiq_coretool.py b/artiq/frontend/artiq_coretool.py index 10bed76a9..92af19734 100755 --- a/artiq/frontend/artiq_coretool.py +++ b/artiq/frontend/artiq_coretool.py @@ -13,7 +13,7 @@ def to_bytes(string): def get_argparser(): parser = argparse.ArgumentParser(description="ARTIQ core device " "remote access tool") - parser.add_argument("--ddb", default="ddb.pyon", + parser.add_argument("-d", "--ddb", default="ddb.pyon", help="device database file") subparsers = parser.add_subparsers(dest="action") diff --git a/artiq/frontend/artiq_run.py b/artiq/frontend/artiq_run.py index 06595746c..069a3358c 100755 --- a/artiq/frontend/artiq_run.py +++ b/artiq/frontend/artiq_run.py @@ -13,7 +13,8 @@ from artiq.language.environment import EnvExperiment from artiq.protocols.file_db import FlatFileDB from artiq.master.worker_db import DeviceManager, ResultDB from artiq.tools import * - +from artiq.compiler.embedding import ObjectMap +from artiq.compiler.targets import OR1KTarget logger = logging.getLogger(__name__) @@ -25,9 +26,13 @@ class ELFRunner(EnvExperiment): def run(self): with open(self.file, "rb") as f: - self.core.comm.load(f.read()) - self.core.comm.run("run") - self.core.comm.serve(dict(), dict()) + kernel_library = f.read() + + target = OR1KTarget() + self.core.comm.load(kernel_library) + self.core.comm.run() + self.core.comm.serve(ObjectMap(), + lambda addresses: target.symbolize(kernel_library, addresses)) class SimpleParamLogger: diff --git a/artiq/language/core.py b/artiq/language/core.py index e83c30de4..1a931d2d9 100644 --- a/artiq/language/core.py +++ b/artiq/language/core.py @@ -182,7 +182,7 @@ def kernel(arg): def inner_decorator(function): @wraps(function) def run_on_core(self, *k_args, **k_kwargs): - return getattr(self, arg).run(function, ((self,) + k_args), k_kwargs) + return getattr(self, arg).run(run_on_core, ((self,) + k_args), k_kwargs) run_on_core.artiq_embedded = _ARTIQEmbeddedInfo( core_name=arg, function=function, syscall=None) return run_on_core From ed236eb7f262e047d0050807675cd964930a949e Mon Sep 17 00:00:00 2001 From: whitequark Date: Fri, 28 Aug 2015 01:54:51 -0500 Subject: [PATCH 250/369] artiq_run: pretty-print diagnostics. --- artiq/frontend/artiq_run.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/artiq/frontend/artiq_run.py b/artiq/frontend/artiq_run.py index 069a3358c..600275368 100755 --- a/artiq/frontend/artiq_run.py +++ b/artiq/frontend/artiq_run.py @@ -12,9 +12,10 @@ import h5py from artiq.language.environment import EnvExperiment from artiq.protocols.file_db import FlatFileDB from artiq.master.worker_db import DeviceManager, ResultDB -from artiq.tools import * +from artiq.coredevice.core import CompileError from artiq.compiler.embedding import ObjectMap from artiq.compiler.targets import OR1KTarget +from artiq.tools import * logger = logging.getLogger(__name__) @@ -122,6 +123,11 @@ def run(with_file=False): exp_inst.prepare() exp_inst.run() exp_inst.analyze() + except CompileError as error: + message = "\n".join(error.__cause__.diagnostic.render(colored=True)) + message = message.replace(os.path.normpath(os.path.join(os.path.dirname(__file__), "..")), + "") + print(message, file=sys.stderr) finally: dmgr.close_devices() From 9936768603f4b8d9f08eb5ebecdc7b6d033b4be9 Mon Sep 17 00:00:00 2001 From: whitequark Date: Fri, 28 Aug 2015 02:11:05 -0500 Subject: [PATCH 251/369] ARTIQIRGenerator: fix non-nullary method calls. --- artiq/compiler/transforms/artiq_ir_generator.py | 14 ++++++++------ lit-test/test/integration/class.py | 3 +++ 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/artiq/compiler/transforms/artiq_ir_generator.py b/artiq/compiler/transforms/artiq_ir_generator.py index 6f6b626f3..f2b8ae062 100644 --- a/artiq/compiler/transforms/artiq_ir_generator.py +++ b/artiq/compiler/transforms/artiq_ir_generator.py @@ -1424,20 +1424,22 @@ class ARTIQIRGenerator(algorithm.Visitor): func = self.visit(node.func) self_arg = None fn_typ = typ + offset = 0 elif types.is_method(typ): method = self.visit(node.func) func = self.append(ir.GetAttr(method, "__func__")) self_arg = self.append(ir.GetAttr(method, "__self__")) fn_typ = types.get_method_function(typ) + offset = 1 args = [None] * (len(fn_typ.args) + len(fn_typ.optargs)) for index, arg_node in enumerate(node.args): arg = self.visit(arg_node) if index < len(fn_typ.args): - args[index] = arg + args[index + offset] = arg else: - args[index] = self.append(ir.Alloc([arg], ir.TOption(arg.type))) + args[index + offset] = self.append(ir.Alloc([arg], ir.TOption(arg.type))) for keyword in node.keywords: arg = self.visit(keyword.value) @@ -1445,19 +1447,19 @@ class ARTIQIRGenerator(algorithm.Visitor): for index, arg_name in enumerate(fn_typ.args): if keyword.arg == arg_name: assert args[index] is None - args[index] = arg + args[index + offset] = arg break elif keyword.arg in fn_typ.optargs: for index, optarg_name in enumerate(fn_typ.optargs): if keyword.arg == optarg_name: assert args[len(fn_typ.args) + index] is None - args[len(fn_typ.args) + index] = \ + args[len(fn_typ.args) + index + offset] = \ 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] = \ + if args[len(fn_typ.args) + index + offset] is None: + args[len(fn_typ.args) + index + offset] = \ self.append(ir.Alloc([], ir.TOption(fn_typ.optargs[optarg_name]))) if self_arg is not None: diff --git a/lit-test/test/integration/class.py b/lit-test/test/integration/class.py index 3d9048a1b..205210ea0 100644 --- a/lit-test/test/integration/class.py +++ b/lit-test/test/integration/class.py @@ -7,7 +7,10 @@ class c: 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 From 9605e8215fea0bac8909585ccdc32fe4f36e66d1 Mon Sep 17 00:00:00 2001 From: whitequark Date: Fri, 28 Aug 2015 02:11:26 -0500 Subject: [PATCH 252/369] coredevice.ttl: update for new int semantics. --- artiq/coredevice/ttl.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/artiq/coredevice/ttl.py b/artiq/coredevice/ttl.py index ffc3ca0c4..59e6f6bd0 100644 --- a/artiq/coredevice/ttl.py +++ b/artiq/coredevice/ttl.py @@ -36,7 +36,7 @@ class TTLOut: self.channel = channel # in RTIO cycles - self.o_previous_timestamp = int64(0) + self.o_previous_timestamp = int(0, width=64) @kernel def set_o(self, o): @@ -100,8 +100,8 @@ class TTLInOut: self.channel = channel # in RTIO cycles - self.o_previous_timestamp = int64(0) - self.i_previous_timestamp = int64(0) + self.o_previous_timestamp = int(0, width=64) + self.i_previous_timestamp = int(0, width=64) @kernel def set_oe(self, oe): @@ -240,7 +240,7 @@ class TTLClockGen: def build(self): # in RTIO cycles - self.previous_timestamp = int64(0) + self.previous_timestamp = int(0, width=64) self.acc_width = 24 @portable From 677cc696435b41570a84951fd35fa373b4cd167c Mon Sep 17 00:00:00 2001 From: whitequark Date: Fri, 28 Aug 2015 02:22:16 -0500 Subject: [PATCH 253/369] compiler.embedding: actually use qualified name when embedding methods. Previous commit 6b55e3b only did this for embedded types. --- artiq/compiler/embedding.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/artiq/compiler/embedding.py b/artiq/compiler/embedding.py index 199c98f0b..1476dce4a 100644 --- a/artiq/compiler/embedding.py +++ b/artiq/compiler/embedding.py @@ -420,7 +420,7 @@ class Stitcher: embedded_function = function.artiq_embedded.function source_code = textwrap.dedent(inspect.getsource(embedded_function)) filename = embedded_function.__code__.co_filename - module_name, _ = os.path.splitext(os.path.basename(filename)) + module_name = embedded_function.__globals__['__name__'] first_line = embedded_function.__code__.co_firstlineno # Extract function environment. @@ -436,7 +436,7 @@ class Stitcher: function_node = parsetree.body[0] # Mangle the name, since we put everything into a single module. - function_node.name = "{}.{}".format(module_name, function_node.name) + function_node.name = "{}.{}".format(module_name, function.__qualname__) # Normally, LocalExtractor would populate the typing environment # of the module with the function name. However, since we run From edf33f164357310e16ec1ff9b84b6b7a7a040c20 Mon Sep 17 00:00:00 2001 From: whitequark Date: Fri, 28 Aug 2015 02:22:35 -0500 Subject: [PATCH 254/369] compiler.targets: dump module signature with ARTIQ_DUMP_SIG=1. --- artiq/compiler/targets.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/artiq/compiler/targets.py b/artiq/compiler/targets.py index 3d9db3a05..bb808bcef 100644 --- a/artiq/compiler/targets.py +++ b/artiq/compiler/targets.py @@ -69,7 +69,11 @@ class Target: def compile(self, module): """Compile the module to a relocatable object for this target.""" - if os.getenv('ARTIQ_DUMP_IR'): + if os.getenv("ARTIQ_DUMP_SIG"): + print("====== MODULE_SIGNATURE DUMP ======", file=sys.stderr) + print(module, file=sys.stderr) + + if os.getenv("ARTIQ_DUMP_IR"): print("====== ARTIQ IR DUMP ======", file=sys.stderr) for function in module.artiq_ir: print(function, file=sys.stderr) @@ -78,7 +82,7 @@ class Target: llparsedmod = llvm.parse_assembly(str(llmod)) llparsedmod.verify() - if os.getenv('ARTIQ_DUMP_LLVM'): + if os.getenv("ARTIQ_DUMP_LLVM"): print("====== LLVM IR DUMP ======", file=sys.stderr) print(str(llparsedmod), file=sys.stderr) @@ -90,7 +94,7 @@ class Target: llpassmgrbuilder.populate(llpassmgr) llpassmgr.run(llparsedmod) - if os.getenv('ARTIQ_DUMP_LLVM'): + if os.getenv("ARTIQ_DUMP_LLVM"): print("====== LLVM IR DUMP (OPTIMIZED) ======", file=sys.stderr) print(str(llparsedmod), file=sys.stderr) @@ -99,7 +103,7 @@ class Target: features=",".join(self.features), reloc="pic", codemodel="default") - if os.getenv('ARTIQ_DUMP_ASSEMBLY'): + if os.getenv("ARTIQ_DUMP_ASSEMBLY"): print("====== ASSEMBLY DUMP ======", file=sys.stderr) print(llmachine.emit_assembly(llparsedmod), file=sys.stderr) @@ -115,7 +119,7 @@ class Target: as results: library = results["output"].read() - if os.getenv('ARTIQ_DUMP_ELF'): + if os.getenv("ARTIQ_DUMP_ELF"): shlib_temp = tempfile.NamedTemporaryFile(suffix=".so", delete=False) shlib_temp.write(library) shlib_temp.close() @@ -145,7 +149,7 @@ class Target: backtrace = [] for function_name, location, address in zip(lines[::2], lines[1::2], addresses): filename, line = location.rsplit(":", 1) - if filename == '??': + if filename == "??": continue # can't get column out of addr2line D: backtrace.append((filename, int(line), -1, function_name, address)) From 6b8ef8c490629a623c1339f0e4370537c73dc2af Mon Sep 17 00:00:00 2001 From: whitequark Date: Fri, 28 Aug 2015 02:22:59 -0500 Subject: [PATCH 255/369] artiq_run: use "artiq_run_" as user code module prefix, not "file_import_". --- artiq/frontend/artiq_run.py | 2 +- artiq/tools.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/artiq/frontend/artiq_run.py b/artiq/frontend/artiq_run.py index 600275368..0943baf7c 100755 --- a/artiq/frontend/artiq_run.py +++ b/artiq/frontend/artiq_run.py @@ -92,7 +92,7 @@ def _build_experiment(dmgr, pdb, rdb, args): "for ELF kernels") return ELFRunner(dmgr, pdb, rdb, file=args.file) else: - module = file_import(args.file) + module = file_import(args.file, prefix="artiq_run_") file = args.file else: module = sys.modules["__main__"] diff --git a/artiq/tools.py b/artiq/tools.py index 767e858dd..445f5c9fc 100644 --- a/artiq/tools.py +++ b/artiq/tools.py @@ -19,7 +19,7 @@ def parse_arguments(arguments): return d -def file_import(filename): +def file_import(filename, prefix="file_import_"): linecache.checkcache(filename) modname = filename @@ -29,7 +29,7 @@ def file_import(filename): i = modname.find(".") if i > 0: modname = modname[:i] - modname = "file_import_" + modname + modname = prefix + modname path = os.path.dirname(os.path.realpath(filename)) sys.path.insert(0, path) From ac92aabce19b48f1a812d5c2e9d3d87fe4e12f4b Mon Sep 17 00:00:00 2001 From: whitequark Date: Fri, 28 Aug 2015 02:50:40 -0500 Subject: [PATCH 256/369] Fix default argument fiasco. --- artiq/compiler/embedding.py | 3 ++- artiq/compiler/types.py | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/artiq/compiler/embedding.py b/artiq/compiler/embedding.py index 1476dce4a..0b903dbe0 100644 --- a/artiq/compiler/embedding.py +++ b/artiq/compiler/embedding.py @@ -105,7 +105,8 @@ class ASTSynthesizer: if typ in self.type_map: instance_type, constructor_type = self.type_map[typ] else: - instance_type = types.TInstance("{}.{}".format(typ.__module__, typ.__qualname__)) + instance_type = types.TInstance("{}.{}".format(typ.__module__, typ.__qualname__), + OrderedDict()) instance_type.attributes['__objectid__'] = builtins.TInt(types.TValue(32)) constructor_type = types.TConstructor(instance_type) diff --git a/artiq/compiler/types.py b/artiq/compiler/types.py index 32501d961..73d53db52 100644 --- a/artiq/compiler/types.py +++ b/artiq/compiler/types.py @@ -359,7 +359,8 @@ class TInstance(TMono): was created """ - def __init__(self, name, attributes=OrderedDict()): + def __init__(self, name, attributes): + assert isinstance(attributes, OrderedDict) super().__init__(name) self.attributes = attributes From 6a29775bf089c46379b7ed74b89d2798411c5cb5 Mon Sep 17 00:00:00 2001 From: whitequark Date: Fri, 28 Aug 2015 02:53:12 -0500 Subject: [PATCH 257/369] compiler.types.Type: more useful __str__. --- artiq/compiler/types.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/artiq/compiler/types.py b/artiq/compiler/types.py index 73d53db52..04e736d65 100644 --- a/artiq/compiler/types.py +++ b/artiq/compiler/types.py @@ -41,7 +41,8 @@ def _map_find(elts): class Type(object): - pass + def __str__(self): + return TypePrinter().name(self) class TVar(Type): """ From 9fd25a1cc4665ea9dc1c52345e944e4a32f5a337 Mon Sep 17 00:00:00 2001 From: whitequark Date: Fri, 28 Aug 2015 03:03:12 -0500 Subject: [PATCH 258/369] LLVMIRGenerator: fix syscall emission. --- artiq/compiler/transforms/llvm_ir_generator.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/artiq/compiler/transforms/llvm_ir_generator.py b/artiq/compiler/transforms/llvm_ir_generator.py index 51218b294..05e3212ab 100644 --- a/artiq/compiler/transforms/llvm_ir_generator.py +++ b/artiq/compiler/transforms/llvm_ir_generator.py @@ -793,10 +793,13 @@ class LLVMIRGenerator: def _prepare_ffi_call(self, insn): llargs = [self.map(arg) for arg in insn.arguments()] - llfunty = ll.FunctionType(self.llty_of_type(insn.type, for_return=True), - [llarg.type for llarg in llargs]) - llfun = ll.Function(self.llmodule, llfunty, - insn.target_function().type.name) + llfunname = insn.target_function().type.name + llfun = self.llmodule.get_global(llfunname) + if llfun is None: + llfunty = ll.FunctionType(self.llty_of_type(insn.type, for_return=True), + [llarg.type for llarg in llargs]) + llfun = ll.Function(self.llmodule, llfunty, + insn.target_function().type.name) return llfun, list(llargs) # See session.c:{send,receive}_rpc_value and comm_generic.py:_{send,receive}_rpc_value. From 30cdb209c6004f7d6c280b583841b35c21bb9bb4 Mon Sep 17 00:00:00 2001 From: whitequark Date: Fri, 28 Aug 2015 03:03:32 -0500 Subject: [PATCH 259/369] log.c: fix warnings. --- soc/runtime/log.c | 1 + soc/runtime/log.h | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/soc/runtime/log.c b/soc/runtime/log.c index 413857564..cb64bea00 100644 --- a/soc/runtime/log.c +++ b/soc/runtime/log.c @@ -1,5 +1,6 @@ #include #include +#include #include #include diff --git a/soc/runtime/log.h b/soc/runtime/log.h index 74e3e815e..f32597a2a 100644 --- a/soc/runtime/log.h +++ b/soc/runtime/log.h @@ -12,6 +12,6 @@ void log_va(const char *fmt, va_list args); void log(const char *fmt, ...); void log_get(char *outbuf); -void log_clear(); +void log_clear(void); #endif /* __LOG_H */ From 98bb570aec7f5087863562f8967c148fa119be9d Mon Sep 17 00:00:00 2001 From: whitequark Date: Fri, 28 Aug 2015 03:06:40 -0500 Subject: [PATCH 260/369] log.c: fix off-by-one error. --- artiq/frontend/artiq_coretool.py | 2 +- soc/runtime/log.c | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/artiq/frontend/artiq_coretool.py b/artiq/frontend/artiq_coretool.py index 92af19734..ed8224511 100755 --- a/artiq/frontend/artiq_coretool.py +++ b/artiq/frontend/artiq_coretool.py @@ -64,7 +64,7 @@ def main(): comm.check_ident() if args.action == "log": - print(comm.get_log()) + print(comm.get_log(), end='') elif args.action == "cfg-read": value = comm.flash_storage_read(args.key) if not value: diff --git a/soc/runtime/log.c b/soc/runtime/log.c index cb64bea00..6ac28fc1e 100644 --- a/soc/runtime/log.c +++ b/soc/runtime/log.c @@ -56,8 +56,8 @@ void log_get(char *outbuf) { int i, j; - j = buffer_index + 1; - for(i=0;i Date: Fri, 28 Aug 2015 03:23:15 -0500 Subject: [PATCH 261/369] transforms.Inferencer: improve attribute unification diagnostic. --- artiq/compiler/embedding.py | 1 + artiq/compiler/transforms/inferencer.py | 15 ++++++++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/artiq/compiler/embedding.py b/artiq/compiler/embedding.py index 0b903dbe0..ed9ec1ad4 100644 --- a/artiq/compiler/embedding.py +++ b/artiq/compiler/embedding.py @@ -10,6 +10,7 @@ from collections import OrderedDict, defaultdict from pythonparser import ast, algorithm, source, diagnostic, parse_buffer +from ..language import core as language_core from . import types, builtins, asttyped, prelude from .transforms import ASTTypedRewriter, Inferencer, IntMonomorphizer diff --git a/artiq/compiler/transforms/inferencer.py b/artiq/compiler/transforms/inferencer.py index aa726e98f..852617c5e 100644 --- a/artiq/compiler/transforms/inferencer.py +++ b/artiq/compiler/transforms/inferencer.py @@ -91,9 +91,22 @@ class Inferencer(algorithm.Visitor): object_type = node.value.type.find() if not types.is_var(object_type): if node.attr 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)}, + node.value.loc) + ] + # Assumes no free type variables in .attributes. self._unify(node.type, object_type.attributes[node.attr], - node.loc, None) + node.loc, None, + makenotes=makenotes, when=" for attribute '{}'".format(node.attr)) elif types.is_instance(object_type) and \ node.attr in object_type.constructor.attributes: # Assumes no free type variables in .attributes. From c621b1f27507fcf4391e9753c205f5674099c0b4 Mon Sep 17 00:00:00 2001 From: whitequark Date: Fri, 28 Aug 2015 03:24:15 -0500 Subject: [PATCH 262/369] compiler: handle language.core.int during embedding. --- artiq/compiler/embedding.py | 4 ++++ artiq/compiler/transforms/llvm_ir_generator.py | 5 +++-- artiq/language/core.py | 4 ++-- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/artiq/compiler/embedding.py b/artiq/compiler/embedding.py index ed9ec1ad4..dbac0fa17 100644 --- a/artiq/compiler/embedding.py +++ b/artiq/compiler/embedding.py @@ -74,6 +74,10 @@ class ASTSynthesizer: typ = builtins.TFloat() return asttyped.NumT(n=value, ctx=None, type=typ, loc=self._add(repr(value))) + elif isinstance(value, language_core.int): + typ = builtins.TInt(width=types.TValue(value.width)) + 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))) diff --git a/artiq/compiler/transforms/llvm_ir_generator.py b/artiq/compiler/transforms/llvm_ir_generator.py index 05e3212ab..ef20056f0 100644 --- a/artiq/compiler/transforms/llvm_ir_generator.py +++ b/artiq/compiler/transforms/llvm_ir_generator.py @@ -6,6 +6,7 @@ into LLVM intermediate representation. import os from pythonparser import ast, diagnostic from llvmlite_artiq import ir as ll +from ...language import core as language_core from .. import types, builtins, ir @@ -1007,8 +1008,8 @@ class LLVMIRGenerator: assert value in (True, False) return ll.Constant(lli1, value) elif builtins.is_int(typ): - assert isinstance(value, int) - return ll.Constant(ll.IntType(builtins.get_int_width(typ)), value) + assert isinstance(value, (int, language_core.int)) + return ll.Constant(ll.IntType(builtins.get_int_width(typ)), int(value)) elif builtins.is_float(typ): assert isinstance(value, float) return ll.Constant(lldouble, value) diff --git a/artiq/language/core.py b/artiq/language/core.py index 1a931d2d9..c87cc8e0f 100644 --- a/artiq/language/core.py +++ b/artiq/language/core.py @@ -72,8 +72,8 @@ class int: return self @property - def width(width): - return width._width + def width(self): + return self._width def __int__(self): return self._value From 72823cf5219599fa4b49dca7109e3a74e9e241a6 Mon Sep 17 00:00:00 2001 From: whitequark Date: Fri, 28 Aug 2015 03:53:43 -0500 Subject: [PATCH 263/369] =?UTF-8?q?test.{coredevice,coredevice=5Fvs=5Fhost?= =?UTF-8?q?}=20=E2=86=92=20test.coredevice.{rtio,portability}.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- artiq/test/{coredevice_vs_host.py => coredevice/portability.py} | 0 artiq/test/{coredevice.py => coredevice/rtio.py} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename artiq/test/{coredevice_vs_host.py => coredevice/portability.py} (100%) rename artiq/test/{coredevice.py => coredevice/rtio.py} (100%) diff --git a/artiq/test/coredevice_vs_host.py b/artiq/test/coredevice/portability.py similarity index 100% rename from artiq/test/coredevice_vs_host.py rename to artiq/test/coredevice/portability.py diff --git a/artiq/test/coredevice.py b/artiq/test/coredevice/rtio.py similarity index 100% rename from artiq/test/coredevice.py rename to artiq/test/coredevice/rtio.py From 79af228af3647958888b450142c72f2189ba9e11 Mon Sep 17 00:00:00 2001 From: whitequark Date: Fri, 28 Aug 2015 04:53:51 -0500 Subject: [PATCH 264/369] ksupport: provide abort() to the kernel library. --- soc/runtime/ksupport.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/soc/runtime/ksupport.c b/soc/runtime/ksupport.c index 8abee3969..c059ea42c 100644 --- a/soc/runtime/ksupport.c +++ b/soc/runtime/ksupport.c @@ -17,6 +17,8 @@ #include "dds.h" #include "rtio.h" +void ksupport_abort(void); + /* compiler-rt symbols */ extern void __divsi3, __modsi3, __ledf2, __gedf2, __unorddf2, __eqdf2, __ltdf2, __nedf2, __gtdf2, __negsf2, __negdf2, __addsf3, __subsf3, __mulsf3, @@ -82,6 +84,7 @@ static const struct symbol runtime_exports[] = { {"__artiq_personality", &__artiq_personality}, {"__artiq_raise", &__artiq_raise}, {"__artiq_reraise", &__artiq_reraise}, + {"abort", &ksupport_abort}, /* proxified syscalls */ {"now_init", &now_init}, @@ -236,6 +239,11 @@ void __artiq_terminate(struct artiq_exception *artiq_exn, while(1); } +void ksupport_abort() { + artiq_raise_from_c("InternalError", "abort() called; check device log for details", + 0, 0, 0); +} + long long int now_init(void) { struct msg_base request; From 5916c10b241d9362f459edfae47c49460e848676 Mon Sep 17 00:00:00 2001 From: whitequark Date: Fri, 28 Aug 2015 04:55:46 -0500 Subject: [PATCH 265/369] ARTIQException: replace ARTIQ library path with in tracebacks. --- artiq/language/core.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/artiq/language/core.py b/artiq/language/core.py index c87cc8e0f..d69cd8a81 100644 --- a/artiq/language/core.py +++ b/artiq/language/core.py @@ -2,7 +2,7 @@ Core ARTIQ extensions to the Python language. """ -import linecache, re +import os, linecache, re from collections import namedtuple from functools import wraps @@ -394,6 +394,8 @@ class ARTIQException(Exception): else: formatted_address = " (RA=0x{:x})".format(address) + filename = filename.replace(os.path.normpath(os.path.join(os.path.dirname(__file__), + "..")), "") if column == -1: lines.append(" File \"{file}\", line {line}, in {function}{address}". format(file=filename, line=line, function=function, From b35051cb08e22edbff44914f4d67e491fa185b5c Mon Sep 17 00:00:00 2001 From: whitequark Date: Fri, 28 Aug 2015 05:13:38 -0500 Subject: [PATCH 266/369] compiler.embedding: handle calls into lambdas (poorly). --- artiq/compiler/embedding.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/artiq/compiler/embedding.py b/artiq/compiler/embedding.py index dbac0fa17..5d6472e3c 100644 --- a/artiq/compiler/embedding.py +++ b/artiq/compiler/embedding.py @@ -471,7 +471,10 @@ class Stitcher: line += 1 source_line = linecache.getline(filename, line) - column = re.search("def", source_line).start(0) + 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) From bcba86de7ec83ecac5f56846f9a29c98ea8cf872 Mon Sep 17 00:00:00 2001 From: whitequark Date: Fri, 28 Aug 2015 05:14:06 -0500 Subject: [PATCH 267/369] compiler.embedding: handle errors during initial kernel call. --- artiq/compiler/embedding.py | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/artiq/compiler/embedding.py b/artiq/compiler/embedding.py index 5d6472e3c..6d46317d1 100644 --- a/artiq/compiler/embedding.py +++ b/artiq/compiler/embedding.py @@ -479,14 +479,17 @@ class Stitcher: return source.Range(source_buffer, column, column) def _call_site_note(self, call_loc, is_syscall): - if is_syscall: - return diagnostic.Diagnostic("note", - "in system call here", {}, - call_loc) + if call_loc: + if is_syscall: + return [diagnostic.Diagnostic("note", + "in system call here", {}, + call_loc)] + else: + return [diagnostic.Diagnostic("note", + "in function called remotely here", {}, + call_loc)] else: - return diagnostic.Diagnostic("note", - "in function called remotely here", {}, - call_loc) + return [] def _extract_annot(self, function, annot, kind, call_loc, is_syscall): if not isinstance(annot, types.Type): @@ -494,7 +497,7 @@ class Stitcher: "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, is_syscall)]) + notes=self._call_site_note(call_loc, is_syscall)) self.engine.process(diag) return types.TVar() @@ -513,7 +516,7 @@ class Stitcher: "system call argument '{argument}' must have a type annotation", {"argument": param.name}, self._function_loc(function), - notes=[self._call_site_note(loc, is_syscall)]) + notes=self._call_site_note(loc, is_syscall)) self.engine.process(diag) elif param.default is not inspect.Parameter.empty: # Try and infer the type from the default value. @@ -530,7 +533,9 @@ class Stitcher: self._function_loc(function)) diag.notes.append(note) - diag.notes.append(self._call_site_note(loc, is_syscall)) + note = self._call_site_note(loc, is_syscall) + if note: + diag.notes += note self.engine.process(diag) @@ -572,7 +577,7 @@ class Stitcher: "system call argument '{argument}' must not have a default value", {"argument": param.name}, self._function_loc(function), - notes=[self._call_site_note(loc, is_syscall=True)]) + notes=self._call_site_note(loc, is_syscall=True)) self.engine.process(diag) if signature.return_annotation is not inspect.Signature.empty: @@ -582,14 +587,14 @@ class Stitcher: diag = diagnostic.Diagnostic("error", "function must have a return type annotation to be called remotely", {}, self._function_loc(function), - notes=[self._call_site_note(loc, is_syscall=False)]) + notes=self._call_site_note(loc, is_syscall=False)) self.engine.process(diag) ret_type = types.TVar() else: # syscall is not None diag = diagnostic.Diagnostic("error", "system call must have a return type annotation", {}, self._function_loc(function), - notes=[self._call_site_note(loc, is_syscall=True)]) + notes=self._call_site_note(loc, is_syscall=True)) self.engine.process(diag) ret_type = types.TVar() From 37811f690babdf97749e606f28c94f30fd319418 Mon Sep 17 00:00:00 2001 From: whitequark Date: Fri, 28 Aug 2015 05:14:52 -0500 Subject: [PATCH 268/369] ARTIQIRGenerator: support comparisons against None. --- artiq/compiler/transforms/artiq_ir_generator.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/artiq/compiler/transforms/artiq_ir_generator.py b/artiq/compiler/transforms/artiq_ir_generator.py index f2b8ae062..bca40580b 100644 --- a/artiq/compiler/transforms/artiq_ir_generator.py +++ b/artiq/compiler/transforms/artiq_ir_generator.py @@ -1134,7 +1134,9 @@ class ARTIQIRGenerator(algorithm.Visitor): assert False def polymorphic_compare_pair_order(self, op, lhs, rhs): - if builtins.is_numeric(lhs.type) and builtins.is_numeric(rhs.type): + 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)) From cbd903a9dcb4ef570832256460f1001e51624a30 Mon Sep 17 00:00:00 2001 From: whitequark Date: Fri, 28 Aug 2015 05:24:57 -0500 Subject: [PATCH 269/369] compiler.embedding: add tests for quote serialization. --- artiq/test/coredevice/embedding.py | 41 ++++++++++++++++++++++++++++++ artiq/test/hardware_testbench.py | 18 ++++++++----- 2 files changed, 52 insertions(+), 7 deletions(-) create mode 100644 artiq/test/coredevice/embedding.py diff --git a/artiq/test/coredevice/embedding.py b/artiq/test/coredevice/embedding.py new file mode 100644 index 000000000..c5ce1a2d4 --- /dev/null +++ b/artiq/test/coredevice/embedding.py @@ -0,0 +1,41 @@ +from artiq.language import * +from artiq.test.hardware_testbench import ExperimentCase + +class Roundtrip(EnvExperiment): + def build(self): + self.attr_device("core") + + @kernel + def roundtrip(self, obj, fn): + fn(obj) + +class RoundtripTest(ExperimentCase): + def assertRoundtrip(self, obj): + exp = self.create(Roundtrip) + def callback(objcopy) -> TNone: + self.assertEqual(obj, objcopy) + exp.roundtrip(obj, callback) + + def test_None(self): + self.assertRoundtrip(None) + + def test_bool(self): + self.assertRoundtrip(True) + self.assertRoundtrip(False) + + def test_int(self): + self.assertRoundtrip(42) + self.assertRoundtrip(int(42, width=64)) + + def test_float(self): + self.assertRoundtrip(42.0) + + def test_str(self): + self.assertRoundtrip("foo") + + def test_list(self): + self.assertRoundtrip([10]) + + def test_object(self): + obj = object() + self.assertRoundtrip(obj) diff --git a/artiq/test/hardware_testbench.py b/artiq/test/hardware_testbench.py index 1734852cc..c135bb696 100644 --- a/artiq/test/hardware_testbench.py +++ b/artiq/test/hardware_testbench.py @@ -37,7 +37,16 @@ class ExperimentCase(unittest.TestCase): self.pdb = FlatFileDB(os.path.join(artiq_root, "pdb.pyon")) self.rdb = ResultDB() - def execute(self, cls, **kwargs): + def create(self, cls, **kwargs): + try: + exp = cls(self.dmgr, self.pdb, self.rdb, **kwargs) + exp.prepare() + return exp + except KeyError as e: + # skip if ddb does not match requirements + raise unittest.SkipTest(*e.args) + + def execute(self, cls, *args, **kwargs): expid = { "file": sys.modules[cls.__module__].__file__, "class_name": cls.__name__, @@ -45,12 +54,7 @@ class ExperimentCase(unittest.TestCase): } self.dmgr.virtual_devices["scheduler"].expid = expid try: - try: - exp = cls(self.dmgr, self.pdb, self.rdb, **kwargs) - except KeyError as e: - # skip if ddb does not match requirements - raise unittest.SkipTest(*e.args) - exp.prepare() + exp = self.create(cls, **kwargs) exp.run() exp.analyze() return exp From 2124ff9e91a0b1629b032186f868f8fba6a5dea7 Mon Sep 17 00:00:00 2001 From: whitequark Date: Fri, 28 Aug 2015 05:28:58 -0500 Subject: [PATCH 270/369] Fix tests. --- artiq/compiler/testbench/embedding.py | 2 +- artiq/compiler/transforms/asttyped_rewriter.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/artiq/compiler/testbench/embedding.py b/artiq/compiler/testbench/embedding.py index 432638f19..7df21af21 100644 --- a/artiq/compiler/testbench/embedding.py +++ b/artiq/compiler/testbench/embedding.py @@ -8,7 +8,7 @@ from artiq.coredevice.core import Core, CompileError def main(): with open(sys.argv[1]) as f: testcase_code = compile(f.read(), f.name, "exec") - testcase_vars = {} + testcase_vars = {'__name__': 'testbench'} exec(testcase_code, testcase_vars) ddb_path = os.path.join(os.path.dirname(sys.argv[1]), "ddb.pyon") diff --git a/artiq/compiler/transforms/asttyped_rewriter.py b/artiq/compiler/transforms/asttyped_rewriter.py index 15f74f8ca..ee93e0834 100644 --- a/artiq/compiler/transforms/asttyped_rewriter.py +++ b/artiq/compiler/transforms/asttyped_rewriter.py @@ -277,7 +277,7 @@ class ASTTypedRewriter(algorithm.Transformer): # 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) + 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 From b263a55b1a9a48fd742a1053c81e4fb8c6f321c1 Mon Sep 17 00:00:00 2001 From: whitequark Date: Sun, 30 Aug 2015 12:23:16 -0500 Subject: [PATCH 271/369] compiler.testbench.perf_embedding: implement. --- artiq/compiler/testbench/__init__.py | 21 +++++++++ artiq/compiler/testbench/perf.py | 46 ++++++------------- artiq/compiler/testbench/perf_embedding.py | 52 ++++++++++++++++++++++ 3 files changed, 87 insertions(+), 32 deletions(-) create mode 100644 artiq/compiler/testbench/perf_embedding.py diff --git a/artiq/compiler/testbench/__init__.py b/artiq/compiler/testbench/__init__.py index e69de29bb..68ea51d7b 100644 --- a/artiq/compiler/testbench/__init__.py +++ b/artiq/compiler/testbench/__init__.py @@ -0,0 +1,21 @@ +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/perf.py b/artiq/compiler/testbench/perf.py index a5b3bdcc0..c01e02376 100644 --- a/artiq/compiler/testbench/perf.py +++ b/artiq/compiler/testbench/perf.py @@ -1,11 +1,12 @@ -import sys, os, time, cProfile as profile, pstats +import sys, os from pythonparser import diagnostic from .. import Module, Source from ..targets import OR1KTarget +from . import benchmark def main(): - if not len(sys.argv) > 1: - print("Expected at least one module filename", file=sys.stderr) + if not len(sys.argv) == 2: + print("Expected exactly one module filename", file=sys.stderr) exit(1) def process_diagnostic(diag): @@ -17,38 +18,19 @@ def main(): engine.process = process_diagnostic # Make sure everything's valid - modules = [Module(Source.from_filename(filename, engine=engine)) - for filename in sys.argv[1:]] + filename = sys.argv[1] + with open(filename) as f: + code = f.read() + source = Source.from_string(code, filename, engine=engine) + module = Module(source) - def benchmark(f, name): - profiler = profile.Profile() - profiler.enable() + benchmark(lambda: Source.from_string(code, filename), + "ARTIQ parsing and inference") - start = time.perf_counter() - end = 0 - runs = 0 - while end - start < 5 or runs < 10: - f() - runs += 1 - end = time.perf_counter() + benchmark(lambda: Module(source), + "ARTIQ transforms and validators") - 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) - - sources = [] - for filename in sys.argv[1:]: - with open(filename) as f: - sources.append(f.read()) - - benchmark(lambda: [Module.from_string(src) for src in sources], - "ARTIQ typechecking and transforms") - - benchmark(lambda: OR1KTarget().compile_and_link(modules), + benchmark(lambda: OR1KTarget().compile_and_link([module]), "LLVM optimization and linking") if __name__ == "__main__": diff --git a/artiq/compiler/testbench/perf_embedding.py b/artiq/compiler/testbench/perf_embedding.py new file mode 100644 index 000000000..258978eac --- /dev/null +++ b/artiq/compiler/testbench/perf_embedding.py @@ -0,0 +1,52 @@ +import sys, os +from pythonparser import diagnostic +from ...protocols.file_db import FlatFileDB +from ...master.worker_db import DeviceManager +from .. import Module +from ..embedding import Stitcher +from ..targets import OR1KTarget +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 open(sys.argv[1]) as f: + testcase_code = compile(f.read(), f.name, "exec") + testcase_vars = {'__name__': 'testbench'} + exec(testcase_code, testcase_vars) + + ddb_path = os.path.join(os.path.dirname(sys.argv[1]), "ddb.pyon") + dmgr = DeviceManager(FlatFileDB(ddb_path)) + + def embed(): + experiment = testcase_vars["Benchmark"](dmgr) + + stitcher = Stitcher() + stitcher.stitch_call(experiment.run, (experiment,), {}) + stitcher.finalize() + return stitcher + + stitcher = embed() + module = Module(stitcher) + + benchmark(lambda: embed(), + "ARTIQ embedding") + + benchmark(lambda: Module(stitcher), + "ARTIQ transforms and validators") + + benchmark(lambda: OR1KTarget().compile_and_link([module]), + "LLVM optimization and linking") + +if __name__ == "__main__": + main() From 5151adb9a8a842794328fd6dfc3dee40572a8d8a Mon Sep 17 00:00:00 2001 From: whitequark Date: Sun, 30 Aug 2015 16:56:58 -0500 Subject: [PATCH 272/369] compiler.targets: correctly pass CPU features to LLVM. --- artiq/compiler/targets.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/artiq/compiler/targets.py b/artiq/compiler/targets.py index bb808bcef..2194b73b5 100644 --- a/artiq/compiler/targets.py +++ b/artiq/compiler/targets.py @@ -100,7 +100,7 @@ class Target: lltarget = llvm.Target.from_triple(self.triple) llmachine = lltarget.create_target_machine( - features=",".join(self.features), + features=",".join(["+{}".format(f) for f in self.features]), reloc="pic", codemodel="default") if os.getenv("ARTIQ_DUMP_ASSEMBLY"): From 501ba912c201f6a40baca0e1da3ca2c71e24d186 Mon Sep 17 00:00:00 2001 From: whitequark Date: Mon, 31 Aug 2015 09:59:33 -0600 Subject: [PATCH 273/369] Implement {delay,now,at}{,_mu} and {mu,seconds}_to_{seconds,mu}. --- artiq/compiler/builtins.py | 24 ++++++ artiq/compiler/module.py | 5 +- artiq/compiler/prelude.py | 17 +++++ artiq/compiler/testbench/jit.py | 6 +- .../compiler/transforms/artiq_ir_generator.py | 36 ++++++++- artiq/compiler/transforms/inferencer.py | 35 +++++++++ .../compiler/transforms/llvm_ir_generator.py | 32 ++++++-- artiq/coredevice/core.py | 2 +- artiq/py2llvm_old/transforms/lower_time.py | 64 ---------------- artiq/py2llvm_old/transforms/quantize_time.py | 70 ------------------ .../Makefile | 2 +- .../__cxxabi_config.h | 0 .../artiq_terminate.c | 0 lit-test/libartiq_support/artiq_time.c | 3 + .../libartiq_support/libartiq_personality.so | Bin 0 -> 23696 bytes lit-test/libartiq_support/libartiq_support.so | Bin 0 -> 24096 bytes .../unwind.h | 0 lit-test/test/lit.cfg | 8 +- lit-test/test/time/advance.py | 9 +++ lit-test/test/time/advance_mu.py | 7 ++ lit-test/test/time/conversion.py | 4 + soc/runtime/ksupport.c | 8 +- 22 files changed, 177 insertions(+), 155 deletions(-) delete mode 100644 artiq/py2llvm_old/transforms/lower_time.py rename lit-test/{libartiq_personality => libartiq_support}/Makefile (50%) rename lit-test/{libartiq_personality => libartiq_support}/__cxxabi_config.h (100%) rename lit-test/{libartiq_personality => libartiq_support}/artiq_terminate.c (100%) create mode 100644 lit-test/libartiq_support/artiq_time.c create mode 100755 lit-test/libartiq_support/libartiq_personality.so create mode 100755 lit-test/libartiq_support/libartiq_support.so rename lit-test/{libartiq_personality => libartiq_support}/unwind.h (100%) create mode 100644 lit-test/test/time/advance.py create mode 100644 lit-test/test/time/advance_mu.py create mode 100644 lit-test/test/time/conversion.py diff --git a/artiq/compiler/builtins.py b/artiq/compiler/builtins.py index 2e26b8f79..4133015cc 100644 --- a/artiq/compiler/builtins.py +++ b/artiq/compiler/builtins.py @@ -138,6 +138,30 @@ def fn_print(): def fn_kernel(): return types.TBuiltinFunction("kernel") +def fn_now(): + return types.TBuiltinFunction("now") + +def fn_delay(): + return types.TBuiltinFunction("delay") + +def fn_at(): + return types.TBuiltinFunction("at") + +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_mu_to_seconds(): + return types.TBuiltinFunction("mu_to_seconds") + +def fn_seconds_to_mu(): + return types.TBuiltinFunction("seconds_to_mu") + # Accessors def is_none(typ): diff --git a/artiq/compiler/module.py b/artiq/compiler/module.py index dbcaf2cd7..1e4232694 100644 --- a/artiq/compiler/module.py +++ b/artiq/compiler/module.py @@ -42,7 +42,7 @@ class Source: return cls(source.Buffer(f.read(), filename, 1), engine=engine) class Module: - def __init__(self, src): + def __init__(self, src, ref_period=1e-6): self.engine = src.engine self.object_map = src.object_map @@ -51,7 +51,8 @@ class Module: monomorphism_validator = validators.MonomorphismValidator(engine=self.engine) escape_validator = validators.EscapeValidator(engine=self.engine) artiq_ir_generator = transforms.ARTIQIRGenerator(engine=self.engine, - module_name=src.name) + module_name=src.name, + ref_period=ref_period) dead_code_eliminator = transforms.DeadCodeEliminator(engine=self.engine) local_access_validator = validators.LocalAccessValidator(engine=self.engine) diff --git a/artiq/compiler/prelude.py b/artiq/compiler/prelude.py index 579a474cf..2929c9c7a 100644 --- a/artiq/compiler/prelude.py +++ b/artiq/compiler/prelude.py @@ -7,17 +7,34 @@ from . import builtins def globals(): return { + # Value constructors "bool": builtins.fn_bool(), "int": builtins.fn_int(), "float": builtins.fn_float(), "list": builtins.fn_list(), "range": builtins.fn_range(), + + # Exception constructors "Exception": builtins.fn_Exception(), "IndexError": builtins.fn_IndexError(), "ValueError": builtins.fn_ValueError(), "ZeroDivisionError": builtins.fn_ZeroDivisionError(), + + # Built-in Python functions "len": builtins.fn_len(), "round": builtins.fn_round(), "print": builtins.fn_print(), + + # ARTIQ decorators "kernel": builtins.fn_kernel(), + + # ARTIQ time management functions + "now": builtins.fn_now(), + "delay": builtins.fn_delay(), + "at": builtins.fn_at(), + "now_mu": builtins.fn_now_mu(), + "delay_mu": builtins.fn_delay_mu(), + "at_mu": builtins.fn_at_mu(), + "mu_to_seconds": builtins.fn_mu_to_seconds(), + "seconds_to_mu": builtins.fn_seconds_to_mu(), } diff --git a/artiq/compiler/testbench/jit.py b/artiq/compiler/testbench/jit.py index 717bdc555..c1c90dd56 100644 --- a/artiq/compiler/testbench/jit.py +++ b/artiq/compiler/testbench/jit.py @@ -5,9 +5,9 @@ from .. import Module, Source from ..targets import NativeTarget def main(): - libartiq_personality = os.getenv('LIBARTIQ_PERSONALITY') - if libartiq_personality is not None: - llvm.load_library_permanently(libartiq_personality) + 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())) diff --git a/artiq/compiler/transforms/artiq_ir_generator.py b/artiq/compiler/transforms/artiq_ir_generator.py index bca40580b..84d3b07e8 100644 --- a/artiq/compiler/transforms/artiq_ir_generator.py +++ b/artiq/compiler/transforms/artiq_ir_generator.py @@ -67,10 +67,11 @@ class ARTIQIRGenerator(algorithm.Visitor): _size_type = builtins.TInt(types.TValue(32)) - def __init__(self, module_name, engine): + 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 @@ -1409,6 +1410,39 @@ class ARTIQIRGenerator(algorithm.Visitor): 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, "now"): + if len(node.args) == 0 and len(node.keywords) == 0: + now_mu = self.append(ir.Builtin("now_mu", [], builtins.TInt(types.TValue(64)))) + now_mu_float = self.append(ir.Coerce(now_mu, builtins.TFloat())) + return self.append(ir.Arith(ast.Mult(loc=None), now_mu_float, self.ref_period)) + else: + assert False + elif types.is_builtin(typ, "delay") or types.is_builtin(typ, "at"): + 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.Coerce(arg_mu_float, builtins.TInt(types.TValue(64)))) + self.append(ir.Builtin(typ.name + "_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_builtin(typ, "mu_to_seconds"): + if len(node.args) == 1 and len(node.keywords) == 0: + arg = self.visit(node.args[0]) + arg_float = self.append(ir.Coerce(arg, builtins.TFloat())) + return self.append(ir.Arith(ast.Mult(loc=None), arg_float, self.ref_period)) + else: + assert False + elif types.is_builtin(typ, "seconds_to_mu"): + if len(node.args) == 1 and len(node.keywords) == 0: + arg = self.visit(node.args[0]) + arg_mu = self.append(ir.Arith(ast.Div(loc=None), arg, self.ref_period)) + return self.append(ir.Coerce(arg_mu, builtins.TInt(types.TValue(64)))) + else: + assert False 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): diff --git a/artiq/compiler/transforms/inferencer.py b/artiq/compiler/transforms/inferencer.py index 852617c5e..d82c32cd0 100644 --- a/artiq/compiler/transforms/inferencer.py +++ b/artiq/compiler/transforms/inferencer.py @@ -505,6 +505,17 @@ class Inferencer(algorithm.Visitor): 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)), @@ -730,6 +741,30 @@ class Inferencer(algorithm.Visitor): pass 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() -> int(width=64)", + [], builtins.TInt(types.TValue(64))) + elif types.is_builtin(typ, "delay_mu"): + simple_form("delay_mu(time_mu:int(width=64)) -> None", + [builtins.TInt(types.TValue(64))]) + elif types.is_builtin(typ, "at_mu"): + simple_form("at_mu(time_mu:int(width=64)) -> None", + [builtins.TInt(types.TValue(64))]) + elif types.is_builtin(typ, "mu_to_seconds"): + simple_form("mu_to_seconds(time_mu:int(width=64)) -> float", + [builtins.TInt(types.TValue(64))], builtins.TFloat()) + elif types.is_builtin(typ, "seconds_to_mu"): + simple_form("seconds_to_mu(time:float) -> int(width=64)", + [builtins.TFloat()], builtins.TInt(types.TValue(64))) elif types.is_constructor(typ): # An user-defined class. self._unify(node.type, typ.find().instance, diff --git a/artiq/compiler/transforms/llvm_ir_generator.py b/artiq/compiler/transforms/llvm_ir_generator.py index ef20056f0..08408c2d6 100644 --- a/artiq/compiler/transforms/llvm_ir_generator.py +++ b/artiq/compiler/transforms/llvm_ir_generator.py @@ -14,6 +14,7 @@ llvoid = ll.VoidType() 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() llmetadata = ll.MetaData() @@ -331,9 +332,9 @@ class LLVMIRGenerator: assert False def llbuiltin(self, name): - llfun = self.llmodule.get_global(name) - if llfun is not None: - return llfun + llglobal = self.llmodule.get_global(name) + if llglobal is not None: + return llglobal if name in "llvm.donothing": llty = ll.FunctionType(llvoid, []) @@ -366,13 +367,19 @@ class LLVMIRGenerator: var_arg=True) elif name == "recv_rpc": llty = ll.FunctionType(lli32, [llptr]) + elif name == "now": + llty = lli64 else: assert False - llfun = ll.Function(self.llmodule, llty, name) - if name in ("__artiq_raise", "__artiq_reraise", "llvm.trap"): - llfun.attributes.add("noreturn") - return llfun + 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") + else: + llglobal = ll.GlobalVariable(self.llmodule, llty, name) + + return llglobal def map(self, value): if isinstance(value, (ir.Argument, ir.Instruction, ir.BasicBlock)): @@ -774,6 +781,17 @@ class LLVMIRGenerator: elif insn.op == "exncast": # This is an identity cast at LLVM IR level. return self.map(insn.operands[0]) + elif insn.op == "now_mu": + return self.llbuilder.load(self.llbuiltin("now"), name=insn.name) + elif insn.op == "delay_mu": + interval, = insn.operands + llnowptr = self.llbuiltin("now") + llnow = self.llbuilder.load(llnowptr) + lladjusted = self.llbuilder.add(llnow, self.map(interval)) + return self.llbuilder.store(lladjusted, llnowptr) + elif insn.op == "at_mu": + time, = insn.operands + return self.llbuilder.store(self.map(time), self.llbuiltin("now")) else: assert False diff --git a/artiq/coredevice/core.py b/artiq/coredevice/core.py index 18cc981c3..afced23bf 100644 --- a/artiq/coredevice/core.py +++ b/artiq/coredevice/core.py @@ -39,7 +39,7 @@ class Core: stitcher.stitch_call(function, args, kwargs) stitcher.finalize() - module = Module(stitcher) + module = Module(stitcher, ref_period=self.ref_period) target = OR1KTarget() library = target.compile_and_link([module]) diff --git a/artiq/py2llvm_old/transforms/lower_time.py b/artiq/py2llvm_old/transforms/lower_time.py deleted file mode 100644 index 5b3df0245..000000000 --- a/artiq/py2llvm_old/transforms/lower_time.py +++ /dev/null @@ -1,64 +0,0 @@ -""" -This transform implements time management functions (delay_mu/now_mu/at_mu) -using an accumulator 'now' and simple replacement rules: - - delay_mu(t) -> now += t - now_mu() -> now - at_mu(t) -> now = t - -The function delay(), that uses seconds, must be lowered to delay_mu() before -invoking this transform. -The accumulator is initialized to an int64 value at the beginning of the -output function. -""" - -import ast - - -class _TimeLowerer(ast.NodeTransformer): - def visit_Call(self, node): - if node.func.id == "now_mu": - return ast.copy_location(ast.Name("now", ast.Load()), node) - else: - self.generic_visit(node) - return node - - def visit_Expr(self, node): - r = node - if isinstance(node.value, ast.Call): - funcname = node.value.func.id - if funcname == "delay_mu": - r = ast.copy_location( - ast.AugAssign(target=ast.Name("now", ast.Store()), - op=ast.Add(), - value=node.value.args[0]), - node) - elif funcname == "at_mu": - r = ast.copy_location( - ast.Assign(targets=[ast.Name("now", ast.Store())], - value=node.value.args[0]), - node) - self.generic_visit(r) - return r - - -def lower_time(func_def): - _TimeLowerer().visit(func_def) - call_init = ast.Call( - func=ast.Name("syscall", ast.Load()), - args=[ast.Str("now_init")], - keywords=[], starargs=None, kwargs=None) - stmt_init = ast.Assign(targets=[ast.Name("now", ast.Store())], - value=call_init) - call_save = ast.Call( - func=ast.Name("syscall", ast.Load()), - args=[ast.Str("now_save"), ast.Name("now", ast.Load())], - keywords=[], starargs=None, kwargs=None) - stmt_save = ast.Expr(call_save) - func_def.body = [ - stmt_init, - ast.Try(body=func_def.body, - handlers=[], - orelse=[], - finalbody=[stmt_save]) - ] diff --git a/artiq/py2llvm_old/transforms/quantize_time.py b/artiq/py2llvm_old/transforms/quantize_time.py index aad75069a..42e04f564 100644 --- a/artiq/py2llvm_old/transforms/quantize_time.py +++ b/artiq/py2llvm_old/transforms/quantize_time.py @@ -1,69 +1,3 @@ -""" -This transform turns calls to delay() that use non-integer time -expressed in seconds into calls to delay_mu() that use int64 time -expressed in multiples of ref_period. - -It does so by inserting multiplication/division/rounding operations around -those calls. - -The seconds_to_mu and mu_to_seconds core language functions are also -implemented here, as well as watchdog to syscall conversion. -""" - -import ast - -from artiq.transforms.tools import value_to_ast - - -def _seconds_to_mu(ref_period, node): - divided = ast.copy_location( - ast.BinOp(left=node, - op=ast.Div(), - right=value_to_ast(ref_period)), - node) - return ast.copy_location( - ast.Call(func=ast.Name("round64", ast.Load()), - args=[divided], - keywords=[], starargs=[], kwargs=[]), - divided) - - -def _mu_to_seconds(ref_period, node): - return ast.copy_location( - ast.BinOp(left=node, - op=ast.Mult(), - right=value_to_ast(ref_period)), - node) - - -class _TimeQuantizer(ast.NodeTransformer): - def __init__(self, ref_period): - self.ref_period = ref_period - self.watchdog_id_counter = 0 - - def visit_Call(self, node): - funcname = node.func.id - if funcname == "delay": - node.func.id = "delay_mu" - if (isinstance(node.args[0], ast.Call) - and node.args[0].func.id == "mu_to_seconds"): - # optimize: - # delay(mu_to_seconds(x)) -> delay_mu(x) - node.args[0] = self.visit(node.args[0].args[0]) - else: - node.args[0] = _seconds_to_mu(self.ref_period, - self.visit(node.args[0])) - return node - elif funcname == "seconds_to_mu": - return _seconds_to_mu(self.ref_period, - self.visit(node.args[0])) - elif funcname == "mu_to_seconds": - return _mu_to_seconds(self.ref_period, - self.visit(node.args[0])) - else: - self.generic_visit(node) - return node - def visit_With(self, node): self.generic_visit(node) if (isinstance(node.items[0].context_expr, ast.Call) @@ -107,7 +41,3 @@ class _TimeQuantizer(ast.NodeTransformer): finalbody=[stmt_clear]) ] return node - - -def quantize_time(func_def, ref_period): - _TimeQuantizer(ref_period).visit(func_def) diff --git a/lit-test/libartiq_personality/Makefile b/lit-test/libartiq_support/Makefile similarity index 50% rename from lit-test/libartiq_personality/Makefile rename to lit-test/libartiq_support/Makefile index 2a72a7185..bac7a11c9 100644 --- a/lit-test/libartiq_personality/Makefile +++ b/lit-test/libartiq_support/Makefile @@ -1,4 +1,4 @@ CC ?= clang -libartiq_personality.so: ../../soc/runtime/artiq_personality.c artiq_terminate.c +libartiq_support.so: ../../soc/runtime/artiq_personality.c artiq_terminate.c artiq_time.c $(CC) -std=c99 -Wall -Werror -I. -I../../soc/runtime -g -fPIC -shared -o $@ $^ diff --git a/lit-test/libartiq_personality/__cxxabi_config.h b/lit-test/libartiq_support/__cxxabi_config.h similarity index 100% rename from lit-test/libartiq_personality/__cxxabi_config.h rename to lit-test/libartiq_support/__cxxabi_config.h diff --git a/lit-test/libartiq_personality/artiq_terminate.c b/lit-test/libartiq_support/artiq_terminate.c similarity index 100% rename from lit-test/libartiq_personality/artiq_terminate.c rename to lit-test/libartiq_support/artiq_terminate.c diff --git a/lit-test/libartiq_support/artiq_time.c b/lit-test/libartiq_support/artiq_time.c new file mode 100644 index 000000000..1afeadbc0 --- /dev/null +++ b/lit-test/libartiq_support/artiq_time.c @@ -0,0 +1,3 @@ +#include + +int64_t now = 0; diff --git a/lit-test/libartiq_support/libartiq_personality.so b/lit-test/libartiq_support/libartiq_personality.so new file mode 100755 index 0000000000000000000000000000000000000000..80ff44cb82344354ecb654dc6bee6b8ed50efdb0 GIT binary patch literal 23696 zcmd6Pdwf*Yz3<*LnPi6~Oe6sUC=N(Mun$F=Q2Ijy$%ELN_f$E&opYI|D`eQ->~wyCz39zEuMzw0sCGf8^S z`R9J_PG+z5`>o$={nl@-z4l&f@4eoJH4f7>^x-tFGzc~3Nlc9@XxywaNNS9gMmElK zjM;+LHCi+#7n~&rFp#L#L$W(ReldU7vtx|Z|#O= zqw(1BX}4d}5V-r&ORswG)xUoF3)K(&8%bF{9Y5{I1E-IeMt;q=iJOVvZ2VSvfBw$2 zOD-CI;&*r5{?ff~N8h+*%Y_?UXA+MM{CV=*Kl{_<|9s^SGe9=s;WToM;oS7%1i&fj zC;?rFvu7MU4IGdD2x!N{ADRGvZUTJu1oD440sX-V@bsexzs%!ZAjY$E>jZiaPe304 z-h*G}(K~^hl_2zF5(EAfe&gA*3vzS}+1Z)^sL-{ejhiRXn*`MbX_ON9vXDQ`SUKO5 zR(cztDMp_0b+<%&LC52mpr39Gw|NAtPt%3oJfnV=#H>^F-%#|K@?QcOWCZuRwRbMe)D>N zFcb~7b;RPK=;rlx;Ye3#b6|ToB+JGx@wfB@C?F8-xD|v~@9;M%33cH>EEb9xf$fnf zl=z{t;}(B3&=Ct6u{bnEjn?jHM_0Vn=nQpsMs|R`xoc-fSJ2-?B5zMis5{;f=}Mv3 zM4~OBprjcU7?L7WyrEd4GenWG5Y#G(YeVt+Mmt0+BVC*5h?FQ)yAw5u!w_X{cPJW* zux{~PcIJjaS6d>`7TVMuYUyb0XjvVI2U6vmLh-dtb_j}t;Xp7LH9|dbcerDFTT6>S zRuPL-EFsjw+zP`V3JNj)?Xj3_A)vZ~#@f0%|I&&|V{JqIsycsFMODRe*_ymMIPvGe zpG-Au03%0DmN*P^%%7A%{C*LOvi~)f?ZTDNX*{apl;wA;a&jbQc3^~b7*DBkef`qc z9Z$ZL+pVrs8h=JzUn^CA(s&IwmU#ptuOS@k^|8fybo zjg-c5c3)hB9N~5r4xJgnQF;?$M*Gld0dFVFkQh24;Ohx9WQUFkxQQ@BW9W#0uO`fp z7&<86RfMw%_X_xO!gSYAw}7h%vx$ec3HVaNY}%nl0na7OCLO8~@JzyNvY|==Pa({v z8uAEu5@9ycP@#YwgxNGhhJgQL7GQRvq2Z5!D1DbOn`r2afZry}t~7L7z`rHTrW!f{ z*f&u98z6iGE+);s1?PRqv%bFblXpyo+uxii-JZL3sk;?;(+^;vqkwZeVzK8Qe5 z3{-zj@Grkt@_$7BXFhWJlEc1d2Cwp&&-s4xQM?4gzO97iOJR~fQ(kZNb&z8u=KK1p z_ksZ?CC|q5ef`x{zz;4$4F`QVZGFyF355CHSbMH1fzf?e8Hv21Pk~a}4gGVJ{vnDS zIlWc*pLt($_Zz{bpLaNirTb3woI)W)G!Occg~*=Y`t1K3UDCK3be0Q! zeY=OR+KGnr4nGc0jHH=5YEs`jg~{^=ydU=N{%|CbbDY&b7lN1%`_8!g&gbvkoIK$h zDBdsJGT@qtSnJPn_*zaQXbJ%M%+n)hjx*xVEk4eUb*}t4`_H*K5;;>MizG5%B3*+` zAC8Q?AbryJP7T`NRC4%$_hhOe`n~WV8(_G1_sNk&E`2!k6?9}+c2ZjA{>i~px^net0US#`DSQI>jiz6Z!Wy$9U-~CVYh9q>HgY6nw&n`mbYeYhd`-etr z()&mE5BWR6A|=ov1U{_D`V9mQ;s&;jl4J>z&nXgV&l#|vvC#;-&fPq!>P@J6)93z? zU69aqZVid3=nV$iVVe-4=v*1grIypkp0*K4Ev~_r|6^oCy0aVcGNldAn&VW+R$H9F5w=J zObdPeuKT8W;9%pE*Q`%IQ=h~r^b_B})L&!V@(nC^WB5Z8iO3wh7k1zR03Pq)<27_< z@;Qt`ebX^Kus*)y*KBU8<cH}=MO0P)1k8iKk`f{7 z@u@HcE*mI#L(1ZP3o7?RLb`HKfqZZ!P8=)xU4MiKGz?9-O|wt^1qOHUJ!a?I4;DCK z4^=K;zTb6qis3=YaIc+Vvm{#23hqCVpoSTmq2M#pz;4Mm&CYir_y)gtUT7Nz!{BX1 z+zEsj-TL0~BzM2#OC}K4u2aPj&Kgf$AtiO&CH<5_MuQ(?=LwH!&!>eO(Ib07kqW2A z#D7r&4>ikT$@0s6Z`OFu6$yF8<*eEDJgMvYyswOW<1d|6T z5@SH$nL-51I+(OUngnBB;t)8n{Ok4#Z%G*={Y5nc(RQJ3;A)Q#*R|v;gR2CU%b5~& zhZJ>_T~q;xM7LD!bjLfxXuaf93wgD9v>az&@*d7(e93_lBn`>^oW=C-XHnl73-;#1 z%6X7^E(e(f$mqf>S-5g&Hp7P{)}qTdv~VEH|E(O0L7zL{X(AZ9xZa?x7U z_{d+VkrNuAKZZ1k3rc?izxBX1gVgnlLJ#yB+Li1IT~c3_UEeP$Nf|9>Rl-s+v3jL< z_bVgusqP;%zv3GZ)k?lb<*$oSl=aMDd0EdFdUwA(62C^Vk!dbl4WmTL70Akq8Ejo7 zZ51IeoPH8|cKXxr52) zQPsn!_%gR>B;HnivOf7jt-jKp6tTPfQG3um^v@9&Wv zNeD^quOT!b6VP}iO}C&KkjXug!*#a61Dg=@YV6-FRc6~&4*i*Ke2N^#E?$+6NhXXK zf^J~yTFG@3L6Hi+?}KZw0w+wlUWOG-up+rX3?cpE>~lXX&Hfwu;^}XA35>>*y1ugKHBo+gvBv>K{n%@A?0iTH`QiG`&(&8vQ z4jYVf@xI-EG2#nhbs*Q3z`e*y=eJDghu+Hi_?lB7Lsr`q#SUps+`6$hz#e-Gyyr+oe07Sjefe@9wV zDe=B?nerMYZyGSH)HYrFNjW7y0F&fM`mH}Q5O4)mciBV#0^7iI-uKKI+^jHs#o&y~ zD1ZUpt32mRzCKuidZ0Z%f$}qa{IhgCo*GL^I3u-QmC{-Xt>?TGu{<+g0rX=g#`_6J zO3ct7XuS8loNZm^}2&QNt}Y@w$! z)+W;KKs3-9tG=N$91bo_owhE(P|Kq{iihmsIAnPI%}sTF?}pX>jcfdyHrLjzTi_`x z^UQ7T3iaUOcsvyJ;6Z;R>Io+BsK3pV5D)l~n`@NDJgs=(9lqRC8dGxh(|Y@Zeow4D zk_ZPqU6Hsa8j2^PUEtK_%V+=-`%SK^HFj;_{lM_YTmLM%R1#CLUvfQoqLRC_9= zp&LRlG0^1+>D2~LJOVw!F|<)>Y6*t|U5RcbSc_eB<@P|!&GBfUCFGA`{loJK50gl1 zO0kV%foJhQD7~R_t2Ah1BJPQ_dScxW>S=}NQYvibB)VDx3DnC|-rZ7N+WoQ8ZZr*z z#e#w0j>9gAX^C{jJGv6^38l4!7Re?s3{QKYD;N$%4Uejv;faL;(Ux{&wA!JrcyyPi zJkY{*n3xB#A^)xb4_0@oODk7~d#>%dh9L_*R2S-D9u}3Syebxh7%#O=o9nOf zd$-hi8#mW)+~BWksNJ+l)*P)K>S(hyMa&b3hD5{165ZWcMG6I#53aPemkM54+i)xx zfNm8LS9*kdJmq#53Crsd1%gh*GdfP*-(n5Gj0xsBH`c zC0=^ulYwvo9Ve)q-xbkxp6&pcVmEhmcY`Zs&8UvfK)j_r6oY-TP|EYt-J@oSc3{lX z+KsKPSkOYDN7ANX#AJCok>|=(c(3UQV^xc-mB3ouc>bgVWaP9&Sj9~U;Of$ZM~iHX zNAXX+HjJ>5TpEYSkQEUXgBWEcVlL4Y)cuhh7%~{SXwm}wd6^zfnH8OHL(pQH@M!tcx=IxP@lSqGt^!G?N zpBov;!WCjZE*|rc@;b2^>1C$;~ffY2Xk(R@*FcGzr3-5H)6=XZw zAW4=7fsUj6?zHlMN|o;e{wkyd_ts{9+k`%urH(GD%&`Lam@+ISMb4^V%xwyw*8 z;mpeA4^v^^2+bDl9P5;+Z`d0%Vg@47Gxt8zzPL8%O zh;kFRlwvJkms6MR(CeUj-BTA)B@eE-^3X8XSb5~jB()C(_wwS<>-%~Gi(Yq~rOG*3 z<1tevhF*WAzj)~KzkW0lA$(lvORZI^3}!9z;DdM^y5GK_=oz~_N@S8+tL-iG2#jJg zPAPgFkDRLVm@X5eTp6VCjVhzpdroQLEA+Lu<;Z*%E*;Qh`2!g)cFa=y_{u01}+z@$6&9fjln7fJQRG5DQa5(pR0*eK3 zBsV~yQUDL;a=qQPL;%NfBLu3y7d88*RozE>9<(hLL=K60503gh70o8o-{ju7#U*OngTnlu|>eX z51@dwle4lftbt%RpNkmo9|Lc=cjIij_d=e-eGy~S{V%{|x$mY{_sby9c4vV;$9)QC z%Wxh6QTC-DfC%yyMk()2piK7>AtMY#miq(bE%&D&qtI|3h8wc0)}f#<0F}<0DIq=p zIEPtJ_Kp{kw`xeX^9U#m>pq;ad(IGT{TWiSZ+!}wLhA$OZ)3j1xZsy4p27-EVGcF@ zG?HwK!m_R1I2TztRvx4lSAZ=imu$uTi13_AbdZtrG_0)2$>HyzG zui%_hummueSGhb|d5nMTL(v(=e6kN!>$NG>WFQBk;BX0|U9mtBdaTqz=MpBql zd>>F!z*I5euv#g28uJ$XXw?ls-HAR94_X%!IJU1JVz zLaEgNdUJ?PVpy}N_ig&kW4VYw$JpCuRT2LV$=j!AqtyJvDd5{{j67B}2z&_x5$jzu ze#qh~`dg2xVdQz3_rD0jnQuXpS;eg5kZ;y?l)RMWjzjIUUIx}&CZTP!*h$UhVh}xa z?S80C40StU~Q{#w*Ts$G+&c5RwkUz*x_ zo7$_j+KU>g<;E{T>P9u8x`V24nD^g8Eg#TnH_{@ zR_&x!&O^ud&U}{gcaq(C=#jnBIX%isI$)QyWZUf9)g^HnNO}AbdS^w+J#aff$DvOd zv$^Yl#}t_uQ=o7bmn6-5{s}0@p)JPj=M;_J8pQ{Gvu5CAe)))?X*6cPrD*h&i^Jip z@3Lm!TqbBX7_;-;$l=hs_K@b|sFrzj4bW(5%wfmlT?^Ee;Lye?#Gy2#N11o#M_z z==>Zexbt3O0L&8?9mk=DdDlP`K#tRBfO)U7$>y0yKo5Vu>VOUW->I@2g%1s3d}4XZ>IGdCQY_gA1}%PQ81Dmo9{zem=wPSnwP=#S{~RVYq?t0vUiWD1_kFjJ*pbAq8n62x0F3go0?7bJm;ZPdqgd_>Uzjp zZd0I7)-s^LnzD!q-d0m~=pXGZd!J6YS;0^5Eqjcz!$LONyzF-*->Hfp207ca$7F96 zO|XtlvP+qAWN#T~rRJ>)KD@Uq!t&b`d~k2s_bBgn1)tb6`(ia*a8T#L$$D7`&ylL&3d!%ZjMBPr>K+lpO_^xnIGpG z34yP&UU#WD4PHE=${8`Dd@9R7uk1d)=i=2Rvf!7fk04k@i|#Xd?Q|X*+*6iE_D_2H>pPsH8Fe^i8QXDI_Y@Dn6y8&0-i&b8t>(@wgIZMjQ{XVmtC1p+OMb?-kW=UB&D^Z_3u^lF#v!twimRk9oC1n-dg?wQ? zXGvL;nJ>=gEGf%Pnv#6ZlClb!_vCYylyw1xl^X?L2H-<>ayK##W0II9WfgYExn3=j2(FFEFR%6rx8s<{5c; zR{{lX(+ub2%k!?ttH-r7OW|1~H%K6Z35#-yOy}ghJW`qft%4NWN|5C$&{bbVaFP~^ zwl&TAB68%Wh}mpwPJ~GS~bWX=Ilu zb(#2i%4T?;0!jhWNClRuDkyONs1RLsWktEFoFZIcwVrD7XPxxFJEGb#*2 zzRF}zakVnrME3jP)RMZ#= z9QxkOxy0=2+lL2G=EFQ$bHuri=j-=9w65wO6p|+UpR*Iz`gN5rwC!*#F5V%Io!3`c zMSTyo-R)RfeDsOy-@DH2;wSKr17G~awzGiu zTZfifFNlg{TNS`hd+)s`V1Q$85&o^a$mb|4#`*f?j)ju_6ofbq;*=G~$we@mphYm3 z;3R_i1PcgGCRj+YFx#;TR~0xwD4Y6_qjI`+-4jO~i>Fx~*5W4}mru14ZPt1#(RY)z z*qY;5I1M$LbM*DThpw~siN8w6x~U1+bAu4`TDG;$QCgfhYF+18Tw-1KI?~IEMbTGS zOG{MasH3(RepYDUN{bRpzFKJ|ZeYIng@>%C?tt(o6K#NY;OBf~Z`}D4!P55HpccC6@;mm;1JU|QgNIS-{VCrLOmF6ZRwKn&4|?(Y-gTn z)pk3sFL_b8!SS&v*@w{3tf0eNAaGlm^0oLa^xPT&l%-sW2WY{o4aEzCXFqzcUJ( z6sYVSXBkp#yg-FoQHB>ZS6}P*`uvSvyr>!Mh+>W1Slk}z3@zT-4q>+>0@0fnUmL+m z*u5wi+97j`@xtPwcqkS}X8V|yGVo$&n@61ZAUxbH-h%XY$@O1&2Rk~})NZbA@HhHu zH+if4-lnFFO$JR;uVdC?+1^{bp}A2~7~18mxOSm(6yGkzB6?)BHTh1XW*Al48VSe}`KzIq>+@!}g zY;0N&mu=d-k?)xTv2mTTeHY#(#f2s`8aQ>_H!-&N$#(OnUs6)GMLPMKE`q2NEshGL z3c6dOp)g;a_teSd7|){4c%my*-4^N!MLSvmaz{rsu6c`AEb}aC^DIJ#u5P*PGS8yc z#`-$pa9{0))eYVz|C*-S^p0`)1`!dP8oa9nXE@SwGn+Aic1)ShP^V-4A}SFSoe|6;^srGYyFrRNusz0#Gtioq z*pTi_JMaQAUtvz!v|eod2#)IFZW-#*T|x|74WX{Kc)J*{IC(Hex1Xd2J3Rog9Q%gV#LhYZW+EYTvQ_6elgUA*dOs0_zt{v?O&40RT*--2Sy6^m(l$%GMsFq z(-Jj0r9udo1W0UA1Gd`iB*Tos3|EOAVFrh#RFiu3xNhi{6OrQI5$F5wdaOVT<8AO1 zQJX%^MTv_YiW=F?Iq z3Hdz(y?-SgkLf3FyfP0bZ@X}dm3cUU)cZv2Wlkd-3x9Tu%sY)7L+?|umpbu?OYdh% z$B+G<49lI^RZ}?0PAT(F>o(5J zwPR%7i4|YHpU7V7#2bcsA5l7fiqSLAV@HX+(u>tJYo>|gLvp-KWY^|b6?rDz?;}zrTs%Yc(meYj?<0}2y zl>E%)O)k!kSFd*`z~|r+#qT@m$0GWBP&0rZ&u`^|eyl^U0=^LaBJ+Dws}(-eZ;cbk zX`29lJMiP#!#ynH)%Sr3=${nyW4~wh(gbpT54`Sc_FA6ze!x^_w>1`OE0{u>A2(JoAZkVXC7)gk?kd{lSR8 zEgab%2>XNaNHpdTBzg=?mAb>Y!wFWbSh0fB?s163$G7AZ3}1V~_J2c8+JcGBPJDAp zP2mJ2zSfmSI%dWW`|Y=(%RuO}(~Z<0?Fh*|=uSChund=Gs*aUcdYfSxt@oGg|-fn`i1FA-)`D`&JnE z)#teM(}1S5ZKjLeiO-bz?cXg^bmCKJqjc&sZy8F*`D7fwla@in zuhZ#{m;P-!?OOfLhtl07z5_R^Tz!JgpZ3AJ3?=e&c%#PKD=BY*(;3nSk^er#`2%k? zaJdkNzTeSl#xwxe`t*bIM7+e!%bYm$eUVOm3XkDa99Vr$J5~bX6jSr3G8&;6sh23t zuV>sfz>!3gr|z@V{g!@j03_nT^dRk^ecZ#Z`StynPHPmkwqMifv>D}mCqTpczD=i^ zKhu6C)(9e_%ZStWcRJ-fmWS4#S^qASk$)A5aOnF$o$B`>$j@88O#U9==o4(u5QqN$ zs803!9IALs`^QjV=hyd@I<3iYxW2aPlzV3E{QCY=r>iqAmYPr7voC{R->2#nyTj5B z`pSNM76A3@_S5&XI^~vf`;n>tE)?4N^?k2SgLY=*GxZc8q-|n6VB#un_O!&v3p^2Jb--+a!#eZ7WU;9n#*X?jRL%+VyZ!ALrj!K!N&YEARKSzn(e*HY4SMeW9amtM5)9ITT z{Q9{;<@|JdEk^Tc`1i;Vr|YlppI0jWV>OymrWqhUq9^Uotf%JBd~%?}NvH2BekpzQ zyjkhrHi{BC&8N}a|B#7e*&~YoK7C`X7&2)KCg9(=LNY|OVZFZ&geFM=}~r+pkj&PZ=r%fy&}z;7{?=n>&m`$R z_x^D{XD74Q`u*1LwSMcj)?RDxwfA0k!y22WF?HD4b&R0yEP<(!1?QV(21yND&eCz7 z!)9?>$7s=*+`eLgQYm8wN05$m{0_+S1H}Tb(z7;B#*WpARPhm-_>M@vBa%<0XJu7X zs!R4Xr1U!|%TrS+j;WMr>1cc_=BNDOxf~pm`URS)RPo&aKC<(_dgMt$;xEL{j^El1 z&Foi?FSxV)>xb_OTv7hQ!y|)-FKvJHuS81aQ}HwXc=o)VW!HS2aMSUdh2JXozr8c% z$}29O_|FIK{^7%KN6y``W#$IQ`S`N~@8!Pz(|2<}eD(L!9(wSAY-un|aM+oXccc zWuB2#dKaL4mc?Fh3A7t@bbO1`Pi4zH3=XT)RIWFRbF<1KqLCaL7cj2}fE3enB%TFd#&lyn$%ED?lQn0jQM{*9KzsjaG=P42L$6BTP}K zb{A?AgCWw|?m#3Orn<#;S(zJrq4v11J+P@e(AwG7*}B>n^O@zF0u?*}b-x*n4m@aoz~WoImS>-^!Y1z)6uAU$WnTd%9< zE%*!z{g4H(d`HC>EqFAHd0e*OlP-1A|0J8PtLo4#c&nfDEO^<3oTbQu&rw2<8Ww!6 z1z%>tTgUTC3*I{ZFSp?3(8i_JSn$?%^jPqdEqXnD@93U>$F`XU^Xxwva}1q8p{MV( z{(>MHSsFpnNJ$K5*X$bP2=1W5;qxOnN^T>VqJ8)*hg%4ykQhG6;ado%kR3k8;U zf~m=dD>|YeyJIrk{^oSycIUQ{^KGu#s~Ow& z0(rTu+%}Z17+_N3tys3Fzq$(ep@pd7kO!x2FFG25(B2 zl)`T4pCk1TlgN>?+qnNJ?@8=F=XuF}nb+qf_eGxeyU%(0nJ3{M^7P|3(5QJ5%|otd z*a7$NT+g_F*LQwz;`=DNm^k&*UnkCb4tbpJ%bq^>#SALlce3XU3L&C-$dkxJ_VTtD z{(E!@<7&`ZW_tQ|UtGBp4e7r496T|SWa_9%eedKYE+24z*t`3~k$A>&s{Vxl#C+Iy z-qm+Gd*|lFNzXvxe(sh5$8^M6f11tHdKN*G1Hhx59XWrTBK|_faq3tX%8pb2xiCi{ zrwe3(KxPZ1W2ouFk&zz=pY**`gElymxOl*Q+H8n^H#|rUaItsy>5;gTd^r3FIx;Lf zEi7~0zXEWQb;r;%OzR#6z^x+>2w|6bw(q1)f`@T&LWDY$H zJ8%I2k9+938ggdhMT|m^PQ~y*_3<3PVRKV0{X7Xr7qAG^?bOZ*M?0XG98cp+hvy~7 zQ#Z-%Ky@9kLvQ{Sodj{e`$I=;5sXR~MtKHueuxnn1~mwS>{f%EP&%{^oX>Ho152;x zQC0mOn1|jF5+Uok$uI>j8^}2)WYKyCmHRe9s&dbRd}ujNG*A!GmPSQxwL6%{7fNTyQr!w?sz!yDNBTb;o_OHhe zgL}W@R|UM9^$8e4L3^{%k#DW+TS7ccJitxl-JuzEK9%k{^{X_G_Od6jN{0dq1<6mL zprALaNtYx769-8o#(=)_c?gzuFlmD@3C8@8hQNWPk6J6d#WY6vi_{E6I=H%l>kSXC zYl&BfR&gqa-4u0?5OteXR1S!Ex0LO4&pQ{EQZVr8OY2>Mir)s!lhGW;nHkNp-mL#*kobOyO>fbPE8a#{s0__ zb^HVwOd&p&Pc5}_ z1pjL!O^pj*6|7`8Md%I!6Z^Li*e?=v;!J*N9X2pI_4Gd|3LSM;(Qoozu=GKz==WYLXa&MKv!9iw}({cwH3W>YKvxT~TmAilJ&>%|pZ8 z3vIo)F!rxMfZV~vOQ`B0RD6kxHxezYZmduIpjKUJPxIJa`m{A@E8nM3z?HUd_W>5a z3fzCr72E@pKO^YRSm_z)hd+t27a9+Fu63Vv-M@!Jq}hFt+(F3uIyutubyL_{A?zWm zuy0-(8F|V5M}95n+uh4t`-(BQq*`GvP8W_N+`ap0F@Po(iKAJ{)otRDiM~vOX)SupJ0b|I;EFpki1vS6I{kAjuY;mwWFisspYZSlXIK z&7!LGKPUt^hO&9(#Z|9QkUwrEfA=DCRJ zmH$cbBL*;?Vi)Gah2(n{w_UixZ1DZOIjCFnCLNxG`MTvQ;M)I1@Ip_uspsEG!NAfb zRy}K>M{&_rho-kfFV)bj5zG%3LG80V5XE?yc!fGz6PS3FFmFK%@EdM&z!U_FI6?C{ z5FPMnXd*e7@*ypT!sD=kU5NGV{v(SmfRTr?gg-G~5=K5jBe@!lzWgpY=)S{+RiF); zw3&kTkd<}^(ZZqZ!W5`&g#GAwkGbwmd?4|o(<)kcVZL?}6Du0D&h*z5t`;+kGyOd@ z-#Fvx@75`85c7AWHI)MIDHADO!^ENi!%A(_O*e`u`2mpt$fw-z4#9!1Ackk<_NsN^lF%V^j6=p`da z(~;)jM{w$|5V#rm&Bd<;zdiUphTp6BUBYh{#wqLX)4&w~w;aDM_?1Lg7}eF)*BSiQ zxltDKbp@(Rq6>_!Xgg24eGy+*wEEVPV9>w7JZ+ngp_Y!a2yU_mV~}BZo15yq?hUKG z8`pR@ZLY0bH{U2NHRd*l0zJ4n9t-#l+~^NS41XNA`rC~-zrl~(Tvie_+HkWwxWXuj zO1bK8z4b=F5$y=agMK3vjv0|aEFKAgQ<*RB{byP+boXD1V`ZJ8wqR#_N35Jbd?=6Y z>J9)EHs(|tmBP>s0hs6u83FaQ!H9*ShdYLBl$u(D0beNIEd?vF3$JVOwcZ|!_*w(r zD4u^9A2%oox0#A96!VRWe^7dBKx5jA{~0B`tcyt^Aukph0{gX=8q zC4*PgHW>B$pj$@7bq05jQD$`!x4a%v!0C8AljmA}!64cv7BI^A!ah+b`I8(VBBwP>RosLC zt}cliN@Qa!f`8I$!EhUiOW_b1q9VLv5F@Qb%*8`~)gOriqt0#dE^-U?H0f9Jm+2)L zmx@&~DJ+cxQmA@LCmsNz9wtIP6O@nfRDi2;)qT8XeDW|b`K^!rsH9nSH?jreR#{1~ zbAh2w^D})hU{{oM;y;sd0Kp@2)HiwcwHfwnw?h_|3hXn#8yUG9>Cc8oM!tfy=IxP@ z(@1}c^fyR1Ult8|oqs!n&S_ob`v^$%a z)|02r$j;DdZ%8q4^yoMbdTRi)+`KipMc2EscIv%s<@9T>T3kFASWdGVX&LMa5~4OY z?><{yPP(lfl0>;5=ordBomBoQvwR=$zeD*wN#%E&`?OUuV_R-HlD`s z1Js{Cb64fS(Lv?E!OwtSo+89JbF0V^c1^v z6p6%qR(qt>;26oq&PaL{k2F=KW2#7)M;fH?-7=%>JSXX}vf+o)hxs|Tzf#g;ro>^T z7t(OmmI{u0qe&Zd2elw^9ed?Z>R33_98~Z$@spv?iXoRFRj?%R^VTg7Rr+a`I&k&oI zUX0kVzmEX5m8>MuHb)I%Uckv#dIKcVt# zn&X4N@@yaBKcsw~egyN1Pi7sN}$7&SnA@ap0J9D*;v78Sor; zt|ZY*xNRq$JE>%ub{-l|vrIqL?%@ooP?6&ZRZ`C(f-g~)>7ODWeVIyi{S=XYjcS|6 z()9u0()D!IEsNTd%+9`u3}LA?IrgH$76AKa069dPo0dMa27+CmgC^z*1J7Jn;jFn9 zK%UK&NipjB1Tbl?Fllw&1@d&)dlWIQr@^H&`v8FSt3Ch`adEGRF`1Y3ra*b47Q z@MdI^gIEUb|EtN!p!=hf-ysqDCfJjaO{KbiH_jP3YkQU(` zMgAmnJr0>?nd?Ic?bTe*fNVr#Q~n**=&sR*Hlb9%2K3r6H3=$Cdfz6$8Tus@Y8NQ> zw(GMA{|=FNO#LP(wcnoszN3a^QAH`BizpEBJZu{68rG`F-)dA1B2R~Q_+=1I{|xb0 zQPwu-nbAy=7ZbT{uw%vvV6`Oz+CF0^%+;3iL3Hq@ncYlVc9wH(K^s#pU4ISdoZG-W zEr-;uAL3YIlaJ06Q1YeZ&d@jrBzlatUR_!FK?1RVmPVc5#>?C&k;J5Y) z=k!P`$pO2BCEI5mmY2*aAf1qx?;gKF^Lr#Q_P zHtV;NMs1y<8-g>^aninUgwr&#Ss5-NKuyVRA(}1JHeX%BX*RG~3nY!wwTEa5P%Z7~ z8cyS3vl}Fh(lz4}>3N#Ei*2w5A-4!Q?NaJaTJ7*VpqqXdFQlw(aLb(ci3y?Ow7)wC zSscPyBQz$E>(hDf=OZG~@xGYDa!BRe`$=!6mb#@@JA4f&x#)T>+T1wI#So5*aj_gC zyz+14?it#1lY{|O4?48NM^RdQA4S3pP1v)w1j|PPsmM0i$d!B(d8a*Zu3#&@o*1ss z=yoc)0xCdVl50xWX=s@D6B2$UmzXd8%vUB{=O-}%F5X2J74wUZZLpzuJwySdaT;B% z_#SGqV(kd%;qT%vk-idc5yf8da`H-v_FGWf2aoM7ewA9UlwaHIgOBYM_RUlF@fsY4 zQm5UJHNXufrY+QDH!A%x#eTU2x0F_rx(W$4ifa||bC*tMIaGo9tz^S0Ud7an%G%*_ zbZk+@I$lNl;Nd-@j&;0__Q6AYL?!EZCGCR;_lR1qmbE;vN7Qn)tYz;WUJVlLwAUmx z#0FW>Ev3iEre=+Lo_+Al9$w3Bsve@2+a>4`we(4_rgSFxpiL7U`g?mz|C5|>yM({H zxAbcyJIH0D%}Z$kuI-e?Pwp-K3AJaBM!l6c!8~e`UDA{zdrN6ns@*B!LwiftQTbgG zKDf8kOY-iP@X0;1R?6Xm26Z}Ua$ov4V!DHyOzqBQUyxLEIpb9GDUS9^iUWH~-=JFD zBjMh?rSFoNeGtmJG-Cl#<7^FUa!qdrEI3i@&VVz-=FV zV^8TnkpYiN`p5Q^K1=jpkrtob0|;G@rfV~Jue?b8&uKq7&)_9j^+KKgI8D)59ZqSi z1!uXYcm$@=nj&?vLbE0tP26oiTaL3`PVnXGKrRX7IzTQx#Ck(5GUUoa&FSr8HjkMf zq-E^lBKcatM(Y!MX$z^MGJ9DkWh0c`$+LG*_D-@w*VFW0B6Vh>f@#igQkG^(X_>vq zvP?cpO3PRdzi097GTAgsO3S8FJ)34pX*mxdpO;Ouq_kYh7iQBeDa}PRMcFhcTwcmGrCdll z=v4xny$aM$yL#7yX_K^)+=487?u^_?T7E_z`l+p$Wo2Cl6zVvI*>hK9U7J;30OKT{ z{OAG+?5Bi^G72<%ZdMjiY5?^dlWjT3oDy`@7Z8}Kgd(Cet-gRbvgfe+f(jJolp;fm z3vE=Ks|@01=AhvUkoovDG}2^k-ZbLNcX0B8sTT6WQSI<~4hTr=6mFtJnK*TnXgT7u zi0emYS~+S$MDwQA7m!D4iaxiBtD5dGD`mrAG-F8;I1`{QJVm8fj1}`)A!b$s91rsFW>ZBx z8mZ_EwFcvU3E=&q3cOBK(X)J+ciEDKK|Jp3S=b(mSKwA%GNoi2OWD#Hi?VXY#7kP1 z=O5*m{xN!A3-`pzJ76W{{ed>*F^JLQpD3~z(_lnrSWt*3jL4(+jTP9T1WIa*CN{OI zv@g>7`u5@0nf4@|^f~<8N9SAiJ+ZFp9~2Tz`Zv&TkDa$v=>>gH zw13*RxbWzSTi(C*{jEozII(r`ffHMg?mJO+^gAc2kgr01(YC+n>z3MgY_YqHz9*j` zf$Qv3%TL&DEz;w9!Rudq;w`SEE#0TmHPKodFA{L?U54N?2M0``ks;P!OOVew+ zZMPJ?%-vwSIzN374NVoaxpO#fOS;|pnk^HRUBxT=@d+!ddi7|3gKfed>iWWNJf#e` zwR!Q{V*N&MB;fOVyJHbaW$qR5;z4#7^R{;QI`RHw(z}=R5PB1e`Kz67yjv zCEmtp^8yK$FHP2oXZb9Sz7HKJ5w_5Lig3%85Q^^&5Kp7=f}?zA)2kl2S{{`1(Sjaj z<9RYS9xA;pzUb)dmg78^&JP%z~mvQpL8 zd=s^;6FUOJqpqiyGZn+A(zc+lJqq2m-ZdMW>fGMu4L8+qSZ&ssn7pm=h_^KyjCX~& zG2%^Bp^rb$_FEoKld~5sgR`l*xtf5#F$^SjZ+OE&KLrH6?P_`_POp(x$=Yvha@Vij zVBK?3+q~AZIfZ%&+u;i?!Yiocu?-uW*286+HgBYtW`WqajGz7t^7iDyg3!me066sT?wgaVPyR)DlU zr5cyHh0B&03)_u_=)={m*IZ*PY-_Bq;|}-KZdl#mZt|{as$K6E!4VHdv8@E#d?-Ln zZP+M6E=q=%aKm;+&^>ssN-kY4d||5C*i4NYrJloI+vWWTTbwNF;M4fxDmL0Lsf)=z z!*_dF3?NQiq!`cHe6=$?lgGa=s2KOq?84S1HPR$rcJDR3b zsFP#8JSq_sU13ZdR3EPu-N2;wwM1z+p+IYvVgsr(?ZDg6^geb)N4P6cv9kkSx+9K8 zthgzR6L0i|{=g2AtH4Xs3uA$344D=Y5ONpKsA;rQh)h#Gh!05(flzy_gO5ctyrp$goUCj5RKx+ZZ3+v$w$MK9d5jE zO?`vgI-t>LVYbzJzPraiT2Z_~P7%nW9i43m{_ZeF2NC{JjP>DeFCXRt)SvN6{0_W= z?pac$BQ-mm~9{C@w6j zFY$nomoqQC?Oy~QBnTQx7K;Sp2wI9{%n-)B@euYGZa7Qp!d7cMLd=_T917L0pN^ z0eKn62*;WHn6pTXoWPVvcXc62!m{;7*{8gW=T|KLS=B+N(c@<4~T4clD<-VS`C z!Gr_{;vnMNB{)?6wpNfqy05FV6?TLn5T|mkMM%Wg8(4X3xT_2MCqWu$iMN9pAB_kA zFF@`!Rn*cF3G7fM{1Pim;2D~#{-eW=<&^sVkCl$T`JndsB;)N&?e9s(r!ln;C>ihI z52CC%%G+5wQ~PkNWp2-*d8-+Oc~| z?UPEz=dhEsi6rH)vt0B0N7f=c!xOD5tr(HFEj9ZW+8sdl-@qJhDMFkM$c_Xd9>9al?Y+kKs0u^{AD2b3>gh zya}m>U1-WbSY7d>jpN6*;{foaw}uR}91kI{jS~;Yfgi74bT66PFXaTKtaB2d`gHp@ z5}*1!n(h#hoYd#e8K?-=OFhfzt^pyAJdV#{>TxRW|MFuN@FXYoDQCIFr#^|SmvYpz z(>y8XCW%*%I~6`4@#-O>ikPUxtEX~>QqDcVkLR~fPk;w{T>2wizKiu1OTjXEg5$@= z6%~)C_c=~Kwtv0E@nie%SxKMzou@ZAer*4xZDaY#CUVGu+iS)+_&LCjM_)Su-Uoah z8~a@=`f^GBIOAl9T>Bl;>0r))hm(=fg-5~L)?MR#A#`8mL0(=7a@$C5m@Z)`#>q`^Jp+{5W z$@vkdAN&2VUrr$BPr$3bY4yg3z|XZJWWEUZbI1D**({D9%dix9xnUx?ur(5k#^P;l zm<4*h*Vi?98|pW0#%-L@?~Hk4UAR{i4q;}bJ}_oMdA=nc@dds9SU3{(`r^8E|z zikR|h(LLURoUdF)Z_${`EOi^JylP1m?zBKBeG*NrW)(cnT`&vd;!3if-wlJ9#k5v2 zp(P~LgqAH|+q(8;$AmY<<$)<>p!X@LB3Yak7XWcBiqD=L=P$qdP(%KtY+^APOvz}!b1YTcyL zlxYLz2FVA_H}TRu-)KuEYJH{B21$-#l^=Mno^&h+L{n9zU#-JbS|k}sFVQG|HFK{4 zjz|>0TCb_}M;Zz(M+(S1D5G~K6u){;LZz!Dud-j!sk9m8^bUrC)%s7RHIhHoeu?Ja zl~rYgQ|m;P(tMZ>r9ZX)A(RonvQxcBqS7<6!o*KY$5j3v;D`jTtpS0vS(ikzgq9A6ua$`4)T@t_$L6Q zU$viF7pwH=cwlQiQuRN8LMwmjcZB^`X5>@#A4%ai*V$JJ#(gRNQ|%{88G9TVTEnRL zQ0sB^-Q`sJRQMV2(tcINuh#7ci-E+EN}mdUA2ej5;#ceYL;pqom%vN0r}))7Z>I38`wxxt zlIfKg#i!ukAVWA+f3*(ZBKeQkC`ys0fcR*ksDElb6@Tg-2o+8$y(IaC^wD!n>hB#z z@topQXxf#LileeWO8!Z5Gqg#sGBVAXfZy>kK|u3;>yfH!#svJwR|u|h6*y8zD*rsm zpX$HgUMCoyPiZj4uTmPGQcUEGkFFK`Rflibrary_info->init; mailbox_send_and_wait(&load_reply); + + now = now_init(); kernel_init(); + now_save(now); struct msg_base finished_reply; finished_reply.type = MESSAGE_TYPE_FINISHED; From 5e0ec3a6ead2ff67f22975a07145307dff1ad929 Mon Sep 17 00:00:00 2001 From: whitequark Date: Mon, 31 Aug 2015 13:42:36 -0600 Subject: [PATCH 274/369] ARTIQIRGenerator: fix keyword/optional argument codegen in calls. --- artiq/compiler/transforms/artiq_ir_generator.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/artiq/compiler/transforms/artiq_ir_generator.py b/artiq/compiler/transforms/artiq_ir_generator.py index 84d3b07e8..9f9551e4b 100644 --- a/artiq/compiler/transforms/artiq_ir_generator.py +++ b/artiq/compiler/transforms/artiq_ir_generator.py @@ -1483,19 +1483,19 @@ class ARTIQIRGenerator(algorithm.Visitor): for index, arg_name in enumerate(fn_typ.args): if keyword.arg == arg_name: assert args[index] is None - args[index + offset] = arg + args[index] = arg break elif keyword.arg in fn_typ.optargs: for index, optarg_name in enumerate(fn_typ.optargs): if keyword.arg == optarg_name: assert args[len(fn_typ.args) + index] is None - args[len(fn_typ.args) + index + offset] = \ + 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 + offset] is None: - args[len(fn_typ.args) + index + offset] = \ + 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: From 3ca5967cea851900a9428215806bd5dc22dce2fe Mon Sep 17 00:00:00 2001 From: whitequark Date: Mon, 31 Aug 2015 14:02:02 -0600 Subject: [PATCH 275/369] LLVMIRGenerator: don't map sret LLVM argument to any ARTIQ arguments. --- artiq/compiler/transforms/llvm_ir_generator.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/artiq/compiler/transforms/llvm_ir_generator.py b/artiq/compiler/transforms/llvm_ir_generator.py index 08408c2d6..0a703fa8b 100644 --- a/artiq/compiler/transforms/llvm_ir_generator.py +++ b/artiq/compiler/transforms/llvm_ir_generator.py @@ -409,11 +409,7 @@ class LLVMIRGenerator: self.llfunction = self.llmodule.get_global(func.name) if self.llfunction is None: - llargtys = [] - for arg in func.arguments: - llargtys.append(self.llty_of_type(arg.type)) - llfunty = ll.FunctionType(args=llargtys, - return_type=self.llty_of_type(func.type.ret, for_return=True)) + llfunty = self.llty_of_type(func.type, bare=True) self.llfunction = ll.Function(self.llmodule, llfunty, func.name) if func.is_internal: @@ -427,7 +423,12 @@ class LLVMIRGenerator: disubprogram = self.debug_info_emitter.emit_subprogram(func, self.llfunction) # First, map arguments. - for arg, llarg in zip(func.arguments, self.llfunction.args): + if self.llfunction.type.pointee.__has_sret: + llactualargs = self.llfunction.args[1:] + else: + llactualargs = self.llfunction.args + + for arg, llarg in zip(func.arguments, llactualargs): self.llmap[arg] = llarg # Second, create all basic blocks. From b03efbc94d158e7df0943b46f224ad8422f00389 Mon Sep 17 00:00:00 2001 From: whitequark Date: Mon, 31 Aug 2015 15:57:08 -0600 Subject: [PATCH 276/369] compiler.embedding: maintain correct column numbers in debug info. --- artiq/compiler/embedding.py | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/artiq/compiler/embedding.py b/artiq/compiler/embedding.py index 6d46317d1..763accb20 100644 --- a/artiq/compiler/embedding.py +++ b/artiq/compiler/embedding.py @@ -5,10 +5,11 @@ the references to the host objects and translates the functions annotated as ``@kernel`` when they are referenced. """ -import os, re, linecache, inspect, textwrap +import sys, os, re, linecache, inspect, textwrap 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 ..language import core as language_core from . import types, builtins, asttyped, prelude @@ -424,7 +425,7 @@ class Stitcher: # Extract function source. embedded_function = function.artiq_embedded.function - source_code = textwrap.dedent(inspect.getsource(embedded_function)) + 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 @@ -436,10 +437,20 @@ class Stitcher: 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) - parsetree, comments = parse_buffer(source_buffer, engine=self.engine) - function_node = parsetree.body[0] + lexer = source_lexer.Lexer(source_buffer, version=sys.version_info[0:2], + diagnostic_engine=self.engine) + lexer.indent = [(initial_indent, + source.Range(source_buffer, 0, len(initial_whitespace)), + initial_whitespace)] + parser = source_parser.Parser(lexer, version=sys.version_info[0:2], + diagnostic_engine=self.engine) + function_node = parser.file_input().body[0] # Mangle the name, since we put everything into a single module. function_node.name = "{}.{}".format(module_name, function.__qualname__) From 2df8b946f9f3b8fecc5ec6b5affe31804fd0e06d Mon Sep 17 00:00:00 2001 From: whitequark Date: Mon, 31 Aug 2015 22:52:39 -0600 Subject: [PATCH 277/369] Factor out the code to pretty-print diagnostics. --- artiq/coredevice/core.py | 18 +++++++++++++++--- artiq/frontend/artiq_compile.py | 7 +++++-- artiq/frontend/artiq_run.py | 6 ++---- artiq/test/hardware_testbench.py | 9 +++++---- 4 files changed, 27 insertions(+), 13 deletions(-) diff --git a/artiq/coredevice/core.py b/artiq/coredevice/core.py index afced23bf..ff8cc41cd 100644 --- a/artiq/coredevice/core.py +++ b/artiq/coredevice/core.py @@ -1,4 +1,4 @@ -import sys +import os from pythonparser import diagnostic @@ -14,7 +14,19 @@ from artiq.coredevice import exceptions class CompileError(Exception): - pass + def __init__(self, diagnostic): + self.diagnostic = diagnostic + + def render_string(self, colored=False): + def shorten_path(path): + return path.replace(os.path.normpath(os.path.join(__file__, "..", "..")), "") + lines = [shorten_path(path) for path in self.diagnostic.render(colored=colored)] + return "\n".join(lines) + + def __str__(self): + # Prepend a newline so that the message shows up on after + # exception class name printed by Python. + return "\n" + self.render_string(colored=True) @syscall @@ -48,7 +60,7 @@ class Core: return stitcher.object_map, stripped_library, \ lambda addresses: target.symbolize(library, addresses) except diagnostic.Error as error: - raise CompileError() from error + raise CompileError(error.diagnostic) from error def run(self, function, args, kwargs): object_map, kernel_library, symbolizer = self.compile(function, args, kwargs) diff --git a/artiq/frontend/artiq_compile.py b/artiq/frontend/artiq_compile.py index 9262edcb3..04ee22cca 100755 --- a/artiq/frontend/artiq_compile.py +++ b/artiq/frontend/artiq_compile.py @@ -1,10 +1,10 @@ #!/usr/bin/env python3 -import logging -import argparse +import sys, logging, argparse from artiq.protocols.file_db import FlatFileDB from artiq.master.worker_db import DeviceManager +from artiq.coredevice.core import CompileError from artiq.tools import * @@ -53,6 +53,9 @@ def main(): object_map, kernel_library, symbolizer = \ core.compile(exp.run, [exp_inst], {}, with_attr_writeback=False) + except CompileError as error: + print(error.render_string(colored=True), file=sys.stderr) + return finally: dmgr.close_devices() diff --git a/artiq/frontend/artiq_run.py b/artiq/frontend/artiq_run.py index 0943baf7c..e7fd06dc4 100755 --- a/artiq/frontend/artiq_run.py +++ b/artiq/frontend/artiq_run.py @@ -124,10 +124,8 @@ def run(with_file=False): exp_inst.run() exp_inst.analyze() except CompileError as error: - message = "\n".join(error.__cause__.diagnostic.render(colored=True)) - message = message.replace(os.path.normpath(os.path.join(os.path.dirname(__file__), "..")), - "") - print(message, file=sys.stderr) + print(error.render_string(colored=True), file=sys.stderr) + return finally: dmgr.close_devices() diff --git a/artiq/test/hardware_testbench.py b/artiq/test/hardware_testbench.py index c135bb696..ab34e46d6 100644 --- a/artiq/test/hardware_testbench.py +++ b/artiq/test/hardware_testbench.py @@ -1,9 +1,7 @@ -import os -import sys -import unittest -import logging +import os, sys, unittest, logging from artiq.language import * +from artiq.coredevice.core import CompileError from artiq.protocols.file_db import FlatFileDB from artiq.master.worker_db import DeviceManager, ResultDB from artiq.frontend.artiq_run import DummyScheduler @@ -58,5 +56,8 @@ class ExperimentCase(unittest.TestCase): exp.run() exp.analyze() return exp + except CompileError as error: + # Reduce amount of text on terminal. + raise error from None finally: self.dmgr.close_devices() From 8762019699468daa0b1a355775ff4391e30d08d5 Mon Sep 17 00:00:00 2001 From: whitequark Date: Mon, 31 Aug 2015 22:55:26 -0600 Subject: [PATCH 278/369] ksupport.c: properly prefix compiler-rt symbols. --- soc/runtime/ksupport.c | 80 +++++++++++++++++++++--------------------- 1 file changed, 40 insertions(+), 40 deletions(-) diff --git a/soc/runtime/ksupport.c b/soc/runtime/ksupport.c index 244dcb7c0..27f3e6f13 100644 --- a/soc/runtime/ksupport.c +++ b/soc/runtime/ksupport.c @@ -40,46 +40,46 @@ struct symbol { static const struct symbol runtime_exports[] = { /* compiler-rt */ - {"divsi3", &__divsi3}, - {"modsi3", &__modsi3}, - {"ledf2", &__ledf2}, - {"gedf2", &__gedf2}, - {"unorddf2", &__unorddf2}, - {"eqdf2", &__eqdf2}, - {"ltdf2", &__ltdf2}, - {"nedf2", &__nedf2}, - {"gtdf2", &__gtdf2}, - {"negsf2", &__negsf2}, - {"negdf2", &__negdf2}, - {"addsf3", &__addsf3}, - {"subsf3", &__subsf3}, - {"mulsf3", &__mulsf3}, - {"divsf3", &__divsf3}, - {"lshrdi3", &__lshrdi3}, - {"muldi3", &__muldi3}, - {"divdi3", &__divdi3}, - {"ashldi3", &__ashldi3}, - {"ashrdi3", &__ashrdi3}, - {"udivmoddi4", &__udivmoddi4}, - {"floatsisf", &__floatsisf}, - {"floatunsisf", &__floatunsisf}, - {"fixsfsi", &__fixsfsi}, - {"fixunssfsi", &__fixunssfsi}, - {"adddf3", &__adddf3}, - {"subdf3", &__subdf3}, - {"muldf3", &__muldf3}, - {"divdf3", &__divdf3}, - {"floatsidf", &__floatsidf}, - {"floatunsidf", &__floatunsidf}, - {"floatdidf", &__floatdidf}, - {"fixdfsi", &__fixdfsi}, - {"fixdfdi", &__fixdfdi}, - {"fixunsdfsi", &__fixunsdfsi}, - {"clzsi2", &__clzsi2}, - {"ctzsi2", &__ctzsi2}, - {"udivdi3", &__udivdi3}, - {"umoddi3", &__umoddi3}, - {"moddi3", &__moddi3}, + {"__divsi3", &__divsi3}, + {"__modsi3", &__modsi3}, + {"__ledf2", &__ledf2}, + {"__gedf2", &__gedf2}, + {"__unorddf2", &__unorddf2}, + {"__eqdf2", &__eqdf2}, + {"__ltdf2", &__ltdf2}, + {"__nedf2", &__nedf2}, + {"__gtdf2", &__gtdf2}, + {"__negsf2", &__negsf2}, + {"__negdf2", &__negdf2}, + {"__addsf3", &__addsf3}, + {"__subsf3", &__subsf3}, + {"__mulsf3", &__mulsf3}, + {"__divsf3", &__divsf3}, + {"__lshrdi3", &__lshrdi3}, + {"__muldi3", &__muldi3}, + {"__divdi3", &__divdi3}, + {"__ashldi3", &__ashldi3}, + {"__ashrdi3", &__ashrdi3}, + {"__udivmoddi4", &__udivmoddi4}, + {"__floatsisf", &__floatsisf}, + {"__floatunsisf", &__floatunsisf}, + {"__fixsfsi", &__fixsfsi}, + {"__fixunssfsi", &__fixunssfsi}, + {"__adddf3", &__adddf3}, + {"__subdf3", &__subdf3}, + {"__muldf3", &__muldf3}, + {"__divdf3", &__divdf3}, + {"__floatsidf", &__floatsidf}, + {"__floatunsidf", &__floatunsidf}, + {"__floatdidf", &__floatdidf}, + {"__fixdfsi", &__fixdfsi}, + {"__fixdfdi", &__fixdfdi}, + {"__fixunsdfsi", &__fixunsdfsi}, + {"__clzsi2", &__clzsi2}, + {"__ctzsi2", &__ctzsi2}, + {"__udivdi3", &__udivdi3}, + {"__umoddi3", &__umoddi3}, + {"__moddi3", &__moddi3}, /* exceptions */ {"_Unwind_Resume", &_Unwind_Resume}, From 956c1985b1b09ebcde4de90fd08c267cea2032de Mon Sep 17 00:00:00 2001 From: whitequark Date: Mon, 31 Aug 2015 23:33:04 -0600 Subject: [PATCH 279/369] ARTIQIRGenerator: evaluate SubscriptT index in non-assignment context. --- .../compiler/transforms/artiq_ir_generator.py | 26 ++++++++++++++++--- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/artiq/compiler/transforms/artiq_ir_generator.py b/artiq/compiler/transforms/artiq_ir_generator.py index 9f9551e4b..11ca96d52 100644 --- a/artiq/compiler/transforms/artiq_ir_generator.py +++ b/artiq/compiler/transforms/artiq_ir_generator.py @@ -799,7 +799,12 @@ class ARTIQIRGenerator(algorithm.Visitor): self.current_assign = old_assign if isinstance(node.slice, ast.Index): - index = self.visit(node.slice.value) + try: + old_assign, self.current_assign = self.current_assign, None + index = self.visit(node.slice.value) + finally: + self.current_assign = old_assign + length = self.iterable_len(value, index.type) mapped_index = self._map_index(length, index, loc=node.begin_loc) @@ -814,21 +819,34 @@ class ARTIQIRGenerator(algorithm.Visitor): length = self.iterable_len(value, node.slice.type) if node.slice.lower is not None: - start_index = self.visit(node.slice.lower) + 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: - stop_index = self.visit(node.slice.upper) + 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 node.slice.step is not None: - step = self.visit(node.slice.step) + 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"), From 156779007acf86ca11be01052815a992bc0f4d4c Mon Sep 17 00:00:00 2001 From: whitequark Date: Mon, 31 Aug 2015 23:33:33 -0600 Subject: [PATCH 280/369] LLVMIRGenerator: implement quoting of lists. --- .../compiler/transforms/llvm_ir_generator.py | 32 +++++++++++++------ 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/artiq/compiler/transforms/llvm_ir_generator.py b/artiq/compiler/transforms/llvm_ir_generator.py index 0a703fa8b..5558265e4 100644 --- a/artiq/compiler/transforms/llvm_ir_generator.py +++ b/artiq/compiler/transforms/llvm_ir_generator.py @@ -1013,28 +1013,42 @@ class LLVMIRGenerator: else: llfields.append(self._quote(getattr(value, attr), typ.attributes[attr], lambda: path() + [attr])) + llconst = ll.Constant(llty.pointee, llfields) - llvalue = ll.Constant(llty.pointee, llfields) - llconst = ll.GlobalVariable(self.llmodule, llvalue.type, global_name) - llconst.initializer = llvalue - llconst.linkage = "private" - self.llobject_map[value_id] = llconst - return llconst + llglobal = ll.GlobalVariable(self.llmodule, llconst.type, global_name) + llglobal.initializer = llconst + llglobal.linkage = "private" + self.llobject_map[value_id] = llglobal + return llglobal elif builtins.is_none(typ): assert value is None return ll.Constant.literal_struct([]) elif builtins.is_bool(typ): assert value in (True, False) - return ll.Constant(lli1, value) + return ll.Constant(llty, value) elif builtins.is_int(typ): assert isinstance(value, (int, language_core.int)) - return ll.Constant(ll.IntType(builtins.get_int_width(typ)), int(value)) + return ll.Constant(llty, int(value)) elif builtins.is_float(typ): assert isinstance(value, float) - return ll.Constant(lldouble, value) + return ll.Constant(llty, value) elif builtins.is_str(typ): assert isinstance(value, (str, bytes)) return self.llstr_of_str(value) + elif builtins.is_list(typ): + assert isinstance(value, list) + elt_type = builtins.get_iterable_elt(typ) + llelts = [self._quote(value[i], elt_type, lambda: path() + [str(i)]) + for i in range(len(value))] + lleltsary = ll.Constant(ll.ArrayType(llelts[0].type, len(llelts)), llelts) + + llglobal = ll.GlobalVariable(self.llmodule, lleltsary.type, "quoted.list") + llglobal.initializer = lleltsary + llglobal.linkage = "private" + + lleltsptr = llglobal.bitcast(lleltsary.type.element.as_pointer()) + llconst = ll.Constant(llty, [ll.Constant(lli32, len(llelts)), lleltsptr]) + return llconst elif types.is_function(typ): # RPC and C functions have no runtime representation; ARTIQ # functions are initialized explicitly. From c9d8fd837e679c4ccdfdbad80f69a8dcddfc4a00 Mon Sep 17 00:00:00 2001 From: whitequark Date: Mon, 31 Aug 2015 23:34:28 -0600 Subject: [PATCH 281/369] test.coredevice.rtio: update for new compiler. --- artiq/test/coredevice/rtio.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/artiq/test/coredevice/rtio.py b/artiq/test/coredevice/rtio.py index e4d9375ba..578ae95ef 100644 --- a/artiq/test/coredevice/rtio.py +++ b/artiq/test/coredevice/rtio.py @@ -2,8 +2,7 @@ from math import sqrt from artiq.language import * from artiq.test.hardware_testbench import ExperimentCase -from artiq.coredevice.runtime_exceptions import RTIOUnderflow -from artiq.coredevice import runtime_exceptions +from artiq.coredevice.exceptions import RTIOUnderflow, RTIOSequenceError class RTT(EnvExperiment): @@ -159,7 +158,7 @@ class TimeKeepsRunning(EnvExperiment): def build(self): self.attr_device("core") - def set_time_at_start(self, time_at_start): + def set_time_at_start(self, time_at_start) -> TNone: self.set_result("time_at_start", time_at_start) @kernel @@ -176,6 +175,7 @@ class Handover(EnvExperiment): self.time_at_start = now_mu() def run(self): + self.time_at_start = int(0, width=64) self.get_now() self.set_result("t1", self.time_at_start) self.get_now() @@ -216,11 +216,11 @@ class CoredeviceTest(ExperimentCase): self.assertEqual(count, npulses) def test_underflow(self): - with self.assertRaises(runtime_exceptions.RTIOUnderflow): + with self.assertRaises(RTIOUnderflow): self.execute(Underflow) def test_sequence_error(self): - with self.assertRaises(runtime_exceptions.RTIOSequenceError): + with self.assertRaises(RTIOSequenceError): self.execute(SequenceError) def test_watchdog(self): @@ -236,7 +236,7 @@ class CoredeviceTest(ExperimentCase): dead_time = mu_to_seconds(t2 - t1, self.dmgr.get("core")) print(dead_time) self.assertGreater(dead_time, 1*ms) - self.assertLess(dead_time, 300*ms) + self.assertLess(dead_time, 500*ms) def test_handover(self): self.execute(Handover) @@ -248,12 +248,11 @@ class RPCTiming(EnvExperiment): self.attr_device("core") self.attr_argument("repeats", FreeValue(100)) - def nop(self, x): + def nop(self, x) -> TNone: pass @kernel def bench(self): - self.ts = [0. for _ in range(self.repeats)] for i in range(self.repeats): t1 = self.core.get_rtio_counter_mu() self.nop(1) @@ -261,6 +260,7 @@ class RPCTiming(EnvExperiment): self.ts[i] = mu_to_seconds(t2 - t1) def run(self): + self.ts = [0. for _ in range(self.repeats)] self.bench() mean = sum(self.ts)/self.repeats self.set_result("rpc_time_stddev", sqrt( From 995245b7869405623283a84564099c3c13c32f6e Mon Sep 17 00:00:00 2001 From: whitequark Date: Tue, 1 Sep 2015 08:38:38 -0600 Subject: [PATCH 282/369] compiler.embedding: default RPC return annotation is -> TNone. --- artiq/compiler/embedding.py | 7 +------ artiq/test/coredevice/embedding.py | 2 +- lit-test/test/embedding/error_rpc_return.py | 14 -------------- 3 files changed, 2 insertions(+), 21 deletions(-) delete mode 100644 lit-test/test/embedding/error_rpc_return.py diff --git a/artiq/compiler/embedding.py b/artiq/compiler/embedding.py index 763accb20..89bdf8f51 100644 --- a/artiq/compiler/embedding.py +++ b/artiq/compiler/embedding.py @@ -595,12 +595,7 @@ class Stitcher: ret_type = self._extract_annot(function, signature.return_annotation, "return type", loc, is_syscall=syscall is not None) elif syscall is None: - diag = diagnostic.Diagnostic("error", - "function must have a return type annotation to be called remotely", {}, - self._function_loc(function), - notes=self._call_site_note(loc, is_syscall=False)) - self.engine.process(diag) - ret_type = types.TVar() + ret_type = builtins.TNone() else: # syscall is not None diag = diagnostic.Diagnostic("error", "system call must have a return type annotation", {}, diff --git a/artiq/test/coredevice/embedding.py b/artiq/test/coredevice/embedding.py index c5ce1a2d4..111f40485 100644 --- a/artiq/test/coredevice/embedding.py +++ b/artiq/test/coredevice/embedding.py @@ -12,7 +12,7 @@ class Roundtrip(EnvExperiment): class RoundtripTest(ExperimentCase): def assertRoundtrip(self, obj): exp = self.create(Roundtrip) - def callback(objcopy) -> TNone: + def callback(objcopy): self.assertEqual(obj, objcopy) exp.roundtrip(obj, callback) diff --git a/lit-test/test/embedding/error_rpc_return.py b/lit-test/test/embedding/error_rpc_return.py deleted file mode 100644 index f3f8ddd70..000000000 --- a/lit-test/test/embedding/error_rpc_return.py +++ /dev/null @@ -1,14 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.embedding %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -from artiq.language.core import * -from artiq.language.types import * - -# CHECK-L: ${LINE:+1}: error: function must have a return type annotation to be called remotely -def foo(): - pass - -@kernel -def entrypoint(): - # CHECK-L: ${LINE:+1}: note: in function called remotely here - foo() From 3af54f5ffc6ba66e3d0df7ccd48d3665f7ca367b Mon Sep 17 00:00:00 2001 From: whitequark Date: Tue, 1 Sep 2015 08:38:53 -0600 Subject: [PATCH 283/369] test.coredevice.rtio: simplify. --- artiq/test/coredevice/rtio.py | 45 ++++++++++------------------------- 1 file changed, 12 insertions(+), 33 deletions(-) diff --git a/artiq/test/coredevice/rtio.py b/artiq/test/coredevice/rtio.py index 578ae95ef..e152b7c51 100644 --- a/artiq/test/coredevice/rtio.py +++ b/artiq/test/coredevice/rtio.py @@ -10,9 +10,6 @@ class RTT(EnvExperiment): self.attr_device("core") self.attr_device("ttl_inout") - def set_rtt(self, rtt): - self.set_result("rtt", rtt) - @kernel def run(self): self.ttl_inout.output() @@ -25,7 +22,7 @@ class RTT(EnvExperiment): delay(1*us) t0 = now_mu() self.ttl_inout.pulse(1*us) - self.set_rtt(mu_to_seconds(self.ttl_inout.timestamp_mu() - t0)) + self.set_result("rtt", mu_to_seconds(self.ttl_inout.timestamp_mu() - t0)) class Loopback(EnvExperiment): @@ -34,9 +31,6 @@ class Loopback(EnvExperiment): self.attr_device("loop_in") self.attr_device("loop_out") - def set_rtt(self, rtt): - self.set_result("rtt", rtt) - @kernel def run(self): self.loop_in.input() @@ -47,7 +41,7 @@ class Loopback(EnvExperiment): delay(1*us) t0 = now_mu() self.loop_out.pulse(1*us) - self.set_rtt(mu_to_seconds(self.loop_in.timestamp_mu() - t0)) + self.set_result("rtt", mu_to_seconds(self.loop_in.timestamp_mu() - t0)) class ClockGeneratorLoopback(EnvExperiment): @@ -56,9 +50,6 @@ class ClockGeneratorLoopback(EnvExperiment): self.attr_device("loop_clock_in") self.attr_device("loop_clock_out") - def set_count(self, count): - self.set_result("count", count) - @kernel def run(self): self.loop_clock_in.input() @@ -69,7 +60,7 @@ class ClockGeneratorLoopback(EnvExperiment): with sequential: delay(200*ns) self.loop_clock_out.set(1*MHz) - self.set_count(self.loop_clock_in.count()) + self.set_result("count", self.loop_clock_in.count()) class PulseRate(EnvExperiment): @@ -77,9 +68,6 @@ class PulseRate(EnvExperiment): self.attr_device("core") self.attr_device("loop_out") - def set_pulse_rate(self, pulse_rate): - self.set_result("pulse_rate", pulse_rate) - @kernel def run(self): dt = seconds_to_mu(1000*ns) @@ -92,7 +80,7 @@ class PulseRate(EnvExperiment): dt += 1 self.core.break_realtime() else: - self.set_pulse_rate(mu_to_seconds(2*dt)) + self.set_result("pulse_rate", mu_to_seconds(2*dt)) break @@ -113,9 +101,6 @@ class LoopbackCount(EnvExperiment): self.attr_device("ttl_inout") self.attr_argument("npulses") - def set_count(self, count): - self.set_result("count", count) - @kernel def run(self): self.ttl_inout.output() @@ -126,7 +111,7 @@ class LoopbackCount(EnvExperiment): for i in range(self.npulses): delay(25*ns) self.ttl_inout.pulse(25*ns) - self.set_count(self.ttl_inout.count()) + self.set_result("count", self.ttl_inout.count()) class Underflow(EnvExperiment): @@ -158,12 +143,9 @@ class TimeKeepsRunning(EnvExperiment): def build(self): self.attr_device("core") - def set_time_at_start(self, time_at_start) -> TNone: - self.set_result("time_at_start", time_at_start) - @kernel def run(self): - self.set_time_at_start(now_mu()) + self.set_result("time_at_start", now_mu()) class Handover(EnvExperiment): @@ -171,15 +153,12 @@ class Handover(EnvExperiment): self.attr_device("core") @kernel - def get_now(self): - self.time_at_start = now_mu() + def get_now(self, var): + self.set_result(var, now_mu()) def run(self): - self.time_at_start = int(0, width=64) - self.get_now() - self.set_result("t1", self.time_at_start) - self.get_now() - self.set_result("t2", self.time_at_start) + self.get_now("t1") + self.get_now("t2") class CoredeviceTest(ExperimentCase): @@ -248,14 +227,14 @@ class RPCTiming(EnvExperiment): self.attr_device("core") self.attr_argument("repeats", FreeValue(100)) - def nop(self, x) -> TNone: + def nop(self): pass @kernel def bench(self): for i in range(self.repeats): t1 = self.core.get_rtio_counter_mu() - self.nop(1) + self.nop() t2 = self.core.get_rtio_counter_mu() self.ts[i] = mu_to_seconds(t2 - t1) From b971cc8cdf55bc8e612bbebe7bbbff4d872cad1a Mon Sep 17 00:00:00 2001 From: whitequark Date: Wed, 2 Sep 2015 17:46:09 -0600 Subject: [PATCH 284/369] compiler.{iodelay,transforms.iodelay_estimator}: implement. --- artiq/compiler/builtins.py | 6 + artiq/compiler/iodelay.py | 285 ++++++++++++++++++ artiq/compiler/module.py | 2 + artiq/compiler/prelude.py | 4 + artiq/compiler/testbench/signature.py | 17 +- artiq/compiler/transforms/__init__.py | 1 + artiq/compiler/transforms/inferencer.py | 10 +- .../compiler/transforms/iodelay_estimator.py | 252 ++++++++++++++++ artiq/compiler/types.py | 23 +- lit-test/test/inferencer/with.py | 5 + lit-test/test/iodelay/argument.py | 7 + lit-test/test/iodelay/arith.py | 8 + lit-test/test/iodelay/call.py | 10 + lit-test/test/iodelay/call_subst.py | 13 + lit-test/test/iodelay/error_arith.py | 21 ++ lit-test/test/iodelay/error_builtinfn.py | 14 + lit-test/test/iodelay/error_call_nested.py | 11 + lit-test/test/iodelay/error_call_subst.py | 13 + lit-test/test/iodelay/error_control_flow.py | 23 ++ lit-test/test/iodelay/error_for.py | 9 + lit-test/test/iodelay/error_goto.py | 14 + lit-test/test/iodelay/error_return.py | 8 + lit-test/test/iodelay/goto.py | 14 + lit-test/test/iodelay/linear.py | 12 + lit-test/test/iodelay/loop.py | 13 + lit-test/test/iodelay/parallel.py | 15 + lit-test/test/iodelay/range.py | 21 ++ lit-test/test/iodelay/return.py | 16 + 28 files changed, 839 insertions(+), 8 deletions(-) create mode 100644 artiq/compiler/iodelay.py create mode 100644 artiq/compiler/transforms/iodelay_estimator.py create mode 100644 lit-test/test/inferencer/with.py create mode 100644 lit-test/test/iodelay/argument.py create mode 100644 lit-test/test/iodelay/arith.py create mode 100644 lit-test/test/iodelay/call.py create mode 100644 lit-test/test/iodelay/call_subst.py create mode 100644 lit-test/test/iodelay/error_arith.py create mode 100644 lit-test/test/iodelay/error_builtinfn.py create mode 100644 lit-test/test/iodelay/error_call_nested.py create mode 100644 lit-test/test/iodelay/error_call_subst.py create mode 100644 lit-test/test/iodelay/error_control_flow.py create mode 100644 lit-test/test/iodelay/error_for.py create mode 100644 lit-test/test/iodelay/error_goto.py create mode 100644 lit-test/test/iodelay/error_return.py create mode 100644 lit-test/test/iodelay/goto.py create mode 100644 lit-test/test/iodelay/linear.py create mode 100644 lit-test/test/iodelay/loop.py create mode 100644 lit-test/test/iodelay/parallel.py create mode 100644 lit-test/test/iodelay/range.py create mode 100644 lit-test/test/iodelay/return.py diff --git a/artiq/compiler/builtins.py b/artiq/compiler/builtins.py index 4133015cc..e4a0c7257 100644 --- a/artiq/compiler/builtins.py +++ b/artiq/compiler/builtins.py @@ -138,6 +138,12 @@ def fn_print(): def fn_kernel(): return types.TBuiltinFunction("kernel") +def fn_parallel(): + return types.TBuiltinFunction("parallel") + +def fn_sequential(): + return types.TBuiltinFunction("sequential") + def fn_now(): return types.TBuiltinFunction("now") diff --git a/artiq/compiler/iodelay.py b/artiq/compiler/iodelay.py new file mode 100644 index 000000000..5cbb3bb37 --- /dev/null +++ b/artiq/compiler/iodelay.py @@ -0,0 +1,285 @@ +""" +The :mod:`iodelay` module contains the classes describing +the statically inferred RTIO delay arising from executing +a function. +""" + +from pythonparser import diagnostic + + +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.value == rhs.value + + 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 free_vars(self): + return self.operand.free_vars() + + def fold(self, vars=None): + return self.__class__(self.operand.fold(vars), + ref_period=self.ref_period) + +class MUToS(Conv): + def __str__(self): + return "mu->s({})".format(self.operand) + + def eval(self, env): + return self.operand.eval(env) * self.ref_period + +class SToMU(Conv): + def __str__(self): + return "s->mu({})".format(self.operand) + + def eval(self, env): + return self.operand.eval(env) / 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 rhs.lhs == 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 Sub(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 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]) + 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) + else: + exprs.append(operand) + if any(consts): + 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) + + +class Delay: + pass + +class Unknown(Delay): + """ + Unknown delay, that is, IO delay that we have not + tried to compute yet. + """ + + def __repr__(self): + return "{}.Unknown()".format(__name__) + +class Indeterminate(Delay): + """ + Indeterminate delay, that is, IO delay that can vary from + invocation to invocation. + + :ivar cause: (:class:`pythonparser.diagnostic.Diagnostic`) + reason for the delay not being inferred + """ + + def __init__(self, cause): + assert isinstance(cause, diagnostic.Diagnostic) + self.cause = cause + + def __repr__(self): + return "<{}.Indeterminate>".format(__name__) + +class Fixed(Delay): + """ + Fixed delay, that is, IO delay that is always the same + for every invocation. + + :ivar length: (int) delay in machine units + """ + + def __init__(self, length): + assert isinstance(length, Expr) + self.length = length + + def __repr__(self): + return "{}.Fixed({})".format(__name__, self.length) + +def is_unknown(delay): + return isinstance(delay, Unknown) + +def is_indeterminate(delay): + return isinstance(delay, Indeterminate) + +def is_fixed(delay, length=None): + if length is None: + return isinstance(delay, Fixed) + else: + return isinstance(delay, Fixed) and delay.length == length diff --git a/artiq/compiler/module.py b/artiq/compiler/module.py index 1e4232694..8c320f8c7 100644 --- a/artiq/compiler/module.py +++ b/artiq/compiler/module.py @@ -50,6 +50,7 @@ class Module: inferencer = transforms.Inferencer(engine=self.engine) monomorphism_validator = validators.MonomorphismValidator(engine=self.engine) escape_validator = validators.EscapeValidator(engine=self.engine) + iodelay_estimator = transforms.IODelayEstimator(ref_period=ref_period) artiq_ir_generator = transforms.ARTIQIRGenerator(engine=self.engine, module_name=src.name, ref_period=ref_period) @@ -62,6 +63,7 @@ class Module: inferencer.visit(src.typedtree) monomorphism_validator.visit(src.typedtree) escape_validator.visit(src.typedtree) + iodelay_estimator.visit_fixpoint(src.typedtree) self.artiq_ir = artiq_ir_generator.visit(src.typedtree) dead_code_eliminator.process(self.artiq_ir) local_access_validator.process(self.artiq_ir) diff --git a/artiq/compiler/prelude.py b/artiq/compiler/prelude.py index 2929c9c7a..26870cb52 100644 --- a/artiq/compiler/prelude.py +++ b/artiq/compiler/prelude.py @@ -28,6 +28,10 @@ def globals(): # ARTIQ decorators "kernel": builtins.fn_kernel(), + # ARTIQ context managers + "parallel": builtins.fn_parallel(), + "sequential": builtins.fn_sequential(), + # ARTIQ time management functions "now": builtins.fn_now(), "delay": builtins.fn_delay(), diff --git a/artiq/compiler/testbench/signature.py b/artiq/compiler/testbench/signature.py index 774259540..d0139a32f 100644 --- a/artiq/compiler/testbench/signature.py +++ b/artiq/compiler/testbench/signature.py @@ -1,6 +1,6 @@ import sys, fileinput from pythonparser import diagnostic -from .. import Module, Source +from .. import types, iodelay, Module, Source def main(): if len(sys.argv) > 1 and sys.argv[1] == "+diag": @@ -13,15 +13,28 @@ def main(): else: diag = False def process_diagnostic(diag): - print("\n".join(diag.render())) + print("\n".join(diag.render(colored=True))) 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 iodelay.is_indeterminate(typ.delay): + process_diagnostic(typ.delay.cause) + print(repr(mod)) except: if not diag: raise diff --git a/artiq/compiler/transforms/__init__.py b/artiq/compiler/transforms/__init__.py index 576ec7e10..39831fea3 100644 --- a/artiq/compiler/transforms/__init__.py +++ b/artiq/compiler/transforms/__init__.py @@ -1,6 +1,7 @@ from .asttyped_rewriter import ASTTypedRewriter from .inferencer import Inferencer from .int_monomorphizer import IntMonomorphizer +from .iodelay_estimator import IODelayEstimator from .artiq_ir_generator import ARTIQIRGenerator from .dead_code_eliminator import DeadCodeEliminator from .llvm_ir_generator import LLVMIRGenerator diff --git a/artiq/compiler/transforms/inferencer.py b/artiq/compiler/transforms/inferencer.py index d82c32cd0..8cc6dc53a 100644 --- a/artiq/compiler/transforms/inferencer.py +++ b/artiq/compiler/transforms/inferencer.py @@ -945,13 +945,19 @@ class Inferencer(algorithm.Visitor): def visit_withitem(self, node): self.generic_visit(node) - if True: # none are supported yet + + typ = node.context_expr.type + if not types.is_builtin(typ, "parallel") or types.is_builtin(typ, "sequential"): diag = diagnostic.Diagnostic("error", "value of type {type} cannot act as a context manager", - {"type": types.TypePrinter().name(node.context_expr.type)}, + {"type": types.TypePrinter().name(typ)}, node.context_expr.loc) self.engine.process(diag) + if node.optional_vars is not None: + self._unify(node.optional_vars.type, node.context_expr.type, + node.optional_vars.loc, node.context_expr.loc) + def visit_ExceptHandlerT(self, node): self.generic_visit(node) diff --git a/artiq/compiler/transforms/iodelay_estimator.py b/artiq/compiler/transforms/iodelay_estimator.py new file mode 100644 index 000000000..eb203c5dd --- /dev/null +++ b/artiq/compiler/transforms/iodelay_estimator.py @@ -0,0 +1,252 @@ +""" +: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 IODelayEstimator(algorithm.Visitor): + def __init__(self, ref_period): + 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): + if isinstance(node, asttyped.NumT): + return iodelay.Const(node.n) + elif isinstance(node, asttyped.CoerceT): + return self.evaluate(node.value, abort) + 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) + rhs = self.evaluate(node.right, abort) + 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", {}, + node.op.loc) + abort([note]) + else: + note = diagnostic.Diagnostic("note", + "this expression is not supported", {}, + node.loc) + abort([note]) + + def abort(self, message, loc, notes=[]): + diag = diagnostic.Diagnostic("error", message, {}, loc, notes=notes) + raise diagnostic.Error(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: + self.visit(node.body) + except (diagnostic.Error, _UnknownDelay): + pass # we don't care; module-level code is never interleaved + + def visit_function(self, args, body, typ): + 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 = iodelay.Fixed(self.current_delay.fold()) + except diagnostic.Error as error: + delay = iodelay.Indeterminate(error.diagnostic) + self.current_delay = old_delay + self.current_return = old_return + self.current_args = old_args + + if iodelay.is_unknown(typ.delay) or iodelay.is_indeterminate(typ.delay): + typ.delay = delay + elif iodelay.is_fixed(typ.delay): + assert typ.delay.value == delay.value + else: + assert False + + 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()) + + def visit_LambdaT(self, node): + self.visit_function(node.args, node.body, node.type.find()) + + def get_iterable_length(self, node): + def abort(notes): + self.abort("for statement cannot be interleaved because " + "trip count is indeterminate", + node.loc, notes) + + def evaluate(node): + return self.evaluate(node, abort) + + 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_For(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 trip count is indeterminate because of control flow", + self.current_goto.loc) + + trip_count = self.get_iterable_length(node.iter) + self.current_delay = old_delay + self.current_delay * 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_While = lambda self, node: self.visit_control_flow("while statement", node) + 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_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, "parallel"): + 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 + + self.current_delay += iodelay.Max(delays) + elif len(node.items) == 1 and types.is_builtin(context_expr.type, "serial"): + 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("this 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) + self.current_delay += iodelay.SToMU(value, ref_period=self.ref_period) + elif types.is_builtin(typ, "delay_mu"): + value = self.evaluate(node.args[0], abort=abort) + self.current_delay += value + elif not types.is_builtin(typ): + if types.is_function(typ): + offset = 0 + elif types.is_method(typ): + offset = 1 + typ = types.get_method_function(typ) + else: + assert False + + delay = typ.find().delay + if iodelay.is_unknown(delay): + raise _UnknownDelay() + elif iodelay.is_indeterminate(delay): + cause = delay.cause + note = diagnostic.Diagnostic("note", + "function called here", {}, + node.loc) + diag = diagnostic.Diagnostic(cause.level, cause.reason, cause.arguments, + cause.location, cause.highlights, cause.notes + [note]) + raise diagnostic.Error(diag) + elif iodelay.is_fixed(delay): + args = {} + for kw_node in node.keywords: + args[kw_node.arg] = kw_node.value + for arg_name, arg_node in zip(typ.args, node.args[offset:]): + args[arg_name] = arg_node + + print(args) + + free_vars = delay.length.free_vars() + self.current_delay += delay.length.fold( + { arg: self.evaluate(args[arg], abort=abort) for arg in free_vars }) + else: + assert False diff --git a/artiq/compiler/types.py b/artiq/compiler/types.py index 04e736d65..ea2d1d875 100644 --- a/artiq/compiler/types.py +++ b/artiq/compiler/types.py @@ -5,6 +5,7 @@ in :mod:`asttyped`. import string from collections import OrderedDict +from . import iodelay class UnificationError(Exception): @@ -191,6 +192,8 @@ class TFunction(Type): optional arguments :ivar ret: (:class:`Type`) return type + :ivar delay: (:class:`iodelay.Delay`) + RTIO delay expression """ attributes = OrderedDict([ @@ -198,11 +201,12 @@ class TFunction(Type): ('__closure__', _TPointer()), ]) - def __init__(self, args, optargs, ret): + def __init__(self, args, optargs, ret, delay=iodelay.Unknown()): assert isinstance(args, OrderedDict) assert isinstance(optargs, OrderedDict) assert isinstance(ret, Type) - self.args, self.optargs, self.ret = args, optargs, ret + assert isinstance(delay, iodelay.Delay) + self.args, self.optargs, self.ret, self.delay = args, optargs, ret, delay def arity(self): return len(self.args) + len(self.optargs) @@ -256,7 +260,8 @@ class TRPCFunction(TFunction): attributes = OrderedDict() def __init__(self, args, optargs, ret, service): - super().__init__(args, optargs, ret) + super().__init__(args, optargs, ret, + delay=iodelay.Fixed(iodelay.Constant(0))) self.service = service def unify(self, other): @@ -278,7 +283,8 @@ class TCFunction(TFunction): attributes = OrderedDict() def __init__(self, args, ret, name): - super().__init__(args, OrderedDict(), ret) + super().__init__(args, OrderedDict(), ret, + delay=iodelay.Fixed(iodelay.Constant(0))) self.name = name def unify(self, other): @@ -547,6 +553,15 @@ class TypePrinter(object): args += ["?%s:%s" % (arg, self.name(typ.optargs[arg])) for arg in typ.optargs] signature = "(%s)->%s" % (", ".join(args), self.name(typ.ret)) + if iodelay.is_unknown(typ.delay) or iodelay.is_fixed(typ.delay, 0): + pass + elif iodelay.is_fixed(typ.delay): + signature += " delay({} mu)".format(typ.delay.length) + elif iodelay.is_indeterminate(typ.delay): + signature += " delay(?)" + else: + assert False + if isinstance(typ, TRPCFunction): return "rpc({}) {}".format(typ.service, signature) if isinstance(typ, TCFunction): diff --git a/lit-test/test/inferencer/with.py b/lit-test/test/inferencer/with.py new file mode 100644 index 000000000..b11f3da13 --- /dev/null +++ b/lit-test/test/inferencer/with.py @@ -0,0 +1,5 @@ +# RUN: %python -m artiq.compiler.testbench.inferencer %s >%t +# RUN: OutputCheck %s --file-to-check=%t + +# CHECK-L: as x: +with parallel as x: pass diff --git a/lit-test/test/iodelay/argument.py b/lit-test/test/iodelay/argument.py new file mode 100644 index 000000000..f026c78f1 --- /dev/null +++ b/lit-test/test/iodelay/argument.py @@ -0,0 +1,7 @@ +# RUN: %python -m artiq.compiler.testbench.signature %s >%t +# RUN: OutputCheck %s --file-to-check=%t + +# CHECK-L: f: (a:float, b:int(width=64))->NoneType delay(s->mu(a) + b mu) +def f(a, b): + delay(a) + delay_mu(b) diff --git a/lit-test/test/iodelay/arith.py b/lit-test/test/iodelay/arith.py new file mode 100644 index 000000000..0eba79715 --- /dev/null +++ b/lit-test/test/iodelay/arith.py @@ -0,0 +1,8 @@ +# RUN: %python -m artiq.compiler.testbench.signature %s >%t +# RUN: OutputCheck %s --file-to-check=%t + +# CHECK-L: f: (a:int(width=32), b:int(width=32), c:int(width=32), d:int(width=32), e:int(width=32))->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/lit-test/test/iodelay/call.py b/lit-test/test/iodelay/call.py new file mode 100644 index 000000000..65580812a --- /dev/null +++ b/lit-test/test/iodelay/call.py @@ -0,0 +1,10 @@ +# RUN: %python -m artiq.compiler.testbench.signature %s >%t +# RUN: OutputCheck %s --file-to-check=%t + +def f(): + delay(1.0) + +# CHECK-L: g: ()->NoneType delay(s->mu(2.0) mu) +def g(): + f() + f() diff --git a/lit-test/test/iodelay/call_subst.py b/lit-test/test/iodelay/call_subst.py new file mode 100644 index 000000000..fddc007c6 --- /dev/null +++ b/lit-test/test/iodelay/call_subst.py @@ -0,0 +1,13 @@ +# 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/lit-test/test/iodelay/error_arith.py b/lit-test/test/iodelay/error_arith.py new file mode 100644 index 000000000..54e30aa24 --- /dev/null +++ b/lit-test/test/iodelay/error_arith.py @@ -0,0 +1,21 @@ +# 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: this 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: this call cannot be interleaved + # CHECK-L: ${LINE:+1}: note: this operator is not supported + delay(2.0**2) + +def h(): + # CHECK-L: ${LINE:+2}: error: this call cannot be interleaved + # CHECK-L: ${LINE:+1}: note: this expression is not supported + delay_mu(1 if False else 2) + +f(1) diff --git a/lit-test/test/iodelay/error_builtinfn.py b/lit-test/test/iodelay/error_builtinfn.py new file mode 100644 index 000000000..ad44c972c --- /dev/null +++ b/lit-test/test/iodelay/error_builtinfn.py @@ -0,0 +1,14 @@ +# 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: this call cannot be interleaved because an argument cannot be statically evaluated + delay_mu(x) + +def g(): + x = 1.0 + # CHECK-L: ${LINE:+1}: error: this call cannot be interleaved + delay(x) + + diff --git a/lit-test/test/iodelay/error_call_nested.py b/lit-test/test/iodelay/error_call_nested.py new file mode 100644 index 000000000..b283c0917 --- /dev/null +++ b/lit-test/test/iodelay/error_call_nested.py @@ -0,0 +1,11 @@ +# 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: this call cannot be interleaved + delay(1.0**2) + +def g(): + # CHECK-L: ${LINE:+1}: note: function called here + f() + f() diff --git a/lit-test/test/iodelay/error_call_subst.py b/lit-test/test/iodelay/error_call_subst.py new file mode 100644 index 000000000..62c6bb29a --- /dev/null +++ b/lit-test/test/iodelay/error_call_subst.py @@ -0,0 +1,13 @@ +# 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: this call cannot be interleaved + pulse(a) diff --git a/lit-test/test/iodelay/error_control_flow.py b/lit-test/test/iodelay/error_control_flow.py new file mode 100644 index 000000000..c179c95b9 --- /dev/null +++ b/lit-test/test/iodelay/error_control_flow.py @@ -0,0 +1,23 @@ +# 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/lit-test/test/iodelay/error_for.py b/lit-test/test/iodelay/error_for.py new file mode 100644 index 000000000..5c5b10ffc --- /dev/null +++ b/lit-test/test/iodelay/error_for.py @@ -0,0 +1,9 @@ +# 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 trip 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/lit-test/test/iodelay/error_goto.py b/lit-test/test/iodelay/error_goto.py new file mode 100644 index 000000000..02686cd86 --- /dev/null +++ b/lit-test/test/iodelay/error_goto.py @@ -0,0 +1,14 @@ +# 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 trip count is indeterminate because of control flow + break + +def g(): + for _ in range(10): + delay_mu(10) + # CHECK-L: ${LINE:+1}: error: loop trip count is indeterminate because of control flow + continue diff --git a/lit-test/test/iodelay/error_return.py b/lit-test/test/iodelay/error_return.py new file mode 100644 index 000000000..c047de2b0 --- /dev/null +++ b/lit-test/test/iodelay/error_return.py @@ -0,0 +1,8 @@ +# 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/lit-test/test/iodelay/goto.py b/lit-test/test/iodelay/goto.py new file mode 100644 index 000000000..d80de43f9 --- /dev/null +++ b/lit-test/test/iodelay/goto.py @@ -0,0 +1,14 @@ +# 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/lit-test/test/iodelay/linear.py b/lit-test/test/iodelay/linear.py new file mode 100644 index 000000000..7037516f6 --- /dev/null +++ b/lit-test/test/iodelay/linear.py @@ -0,0 +1,12 @@ +# RUN: %python -m artiq.compiler.testbench.signature %s >%t +# RUN: OutputCheck %s --file-to-check=%t + +# CHECK-L: f: ()->NoneType delay(s->mu(1.0) + 1000 mu) +def f(): + delay(1.0) + delay_mu(1000) + +# CHECK-L: g: ()->NoneType delay(s->mu(5.0) mu) +def g(): + delay(1.0) + delay(2.0 * 2) diff --git a/lit-test/test/iodelay/loop.py b/lit-test/test/iodelay/loop.py new file mode 100644 index 000000000..228756224 --- /dev/null +++ b/lit-test/test/iodelay/loop.py @@ -0,0 +1,13 @@ +# RUN: %python -m artiq.compiler.testbench.signature %s >%t +# RUN: OutputCheck %s --file-to-check=%t + +# CHECK-L: f: ()->NoneType delay(s->mu(1.5) * 10 mu) +def f(): + for _ in range(10): + delay(1.5) + +# CHECK-L: g: ()->NoneType delay(s->mu(1.5) * 2 * 10 mu) +def g(): + for _ in range(10): + for _ in range(2): + delay(1.5) diff --git a/lit-test/test/iodelay/parallel.py b/lit-test/test/iodelay/parallel.py new file mode 100644 index 000000000..0976f1780 --- /dev/null +++ b/lit-test/test/iodelay/parallel.py @@ -0,0 +1,15 @@ +# RUN: %python -m artiq.compiler.testbench.signature %s >%t +# RUN: OutputCheck %s --file-to-check=%t + +# CHECK-L: f: (a:int(width=64), b:int(width=64))->NoneType delay(max(a, b) mu) +def f(a, b): + with parallel: + delay_mu(a) + delay_mu(b) + +# CHECK-L: g: (a:int(width=64))->NoneType delay(max(a, 200) mu) +def g(a): + with parallel: + delay_mu(100) + delay_mu(200) + delay_mu(a) diff --git a/lit-test/test/iodelay/range.py b/lit-test/test/iodelay/range.py new file mode 100644 index 000000000..498ee6eb5 --- /dev/null +++ b/lit-test/test/iodelay/range.py @@ -0,0 +1,21 @@ +# RUN: %python -m artiq.compiler.testbench.signature %s >%t +# RUN: OutputCheck %s --file-to-check=%t + +# CHECK-L: f: (a:int(width=32))->NoneType delay(s->mu(1.5) * a mu) +def f(a): + for _ in range(a): + delay(1.5) + +# CHECK-L: g: (a:int(width=32), b:int(width=32))->NoneType delay(s->mu(1.5) * (b - a) mu) +def g(a, b): + for _ in range(a, b): + delay(1.5) + +# CHECK-L: h: (a:int(width=32), b:int(width=32), c:int(width=32))->NoneType delay(s->mu(1.5) * (b - a) // c mu) +def h(a, b, c): + for _ in range(a, b, c): + delay(1.5) + +f(1) +g(1,2) +h(1,2,3) diff --git a/lit-test/test/iodelay/return.py b/lit-test/test/iodelay/return.py new file mode 100644 index 000000000..3a3dc09fa --- /dev/null +++ b/lit-test/test/iodelay/return.py @@ -0,0 +1,16 @@ +# RUN: %python -m artiq.compiler.testbench.signature %s >%t +# RUN: OutputCheck %s --file-to-check=%t + +# CHECK-L: f: ()->int(width=32) delay(s->mu(1.5) * 10 mu) +def f(): + for _ in range(10): + delay(1.5) + return 10 + +# CHECK-L: g: (x:float)->int(width=32) delay(0 mu) +def g(x): + if x > 1.0: + return 1 + return 0 + +g(1.0) From 867a0689adb969dd261f2cfe63fbaf7702d4ae8a Mon Sep 17 00:00:00 2001 From: whitequark Date: Wed, 2 Sep 2015 17:46:54 -0600 Subject: [PATCH 285/369] transforms.Inferencer: narrow range() element type. --- artiq/compiler/transforms/inferencer.py | 28 ++++++------------- .../test/inferencer/error_builtin_calls.py | 3 -- 2 files changed, 8 insertions(+), 23 deletions(-) diff --git a/artiq/compiler/transforms/inferencer.py b/artiq/compiler/transforms/inferencer.py index 8cc6dc53a..8ca560285 100644 --- a/artiq/compiler/transforms/inferencer.py +++ b/artiq/compiler/transforms/inferencer.py @@ -656,33 +656,21 @@ class Inferencer(algorithm.Visitor): diagnose(valid_forms()) elif types.is_builtin(typ, "range"): valid_forms = lambda: [ - valid_form("range(max:'a) -> range(elt='a)"), - valid_form("range(min:'a, max:'a) -> range(elt='a)"), - valid_form("range(min:'a, max:'a, step:'a) -> range(elt='a)"), + valid_form("range(max:int(width='a)) -> range(elt=int(width='a))"), + valid_form("range(min:int(width='a), max:int(width='a)) " + "-> range(elt=int(width='a))"), + valid_form("range(min:int(width='a), max:int(width='a), " + "step:int(width='a)) -> range(elt=int(width='a))"), ] - range_tvar = types.TVar() - self._unify(node.type, builtins.TRange(range_tvar), + 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_tvar, + self._unify(arg.type, range_elt, arg.loc, None) - - if builtins.is_int(arg.type): - pass - 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", - "an argument of range() must be of an integer type", {}, - node.func.loc, notes=[note]) - self.engine.process(diag) else: diagnose(valid_forms()) elif types.is_builtin(typ, "len"): diff --git a/lit-test/test/inferencer/error_builtin_calls.py b/lit-test/test/inferencer/error_builtin_calls.py index 8eab7a203..aae74c5cd 100644 --- a/lit-test/test/inferencer/error_builtin_calls.py +++ b/lit-test/test/inferencer/error_builtin_calls.py @@ -10,6 +10,3 @@ len(1) # CHECK-L: ${LINE:+1}: error: the argument of list() must be of an iterable type list(1) - -# CHECK-L: ${LINE:+1}: error: an argument of range() must be of an integer type -range([]) From 1437fff17bf32f19fd3fe9bfbb8c5dcc78fa4834 Mon Sep 17 00:00:00 2001 From: whitequark Date: Wed, 2 Sep 2015 17:47:19 -0600 Subject: [PATCH 286/369] language.core.int64: implement __{,i,r}truediv__. --- artiq/language/core.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/artiq/language/core.py b/artiq/language/core.py index d69cd8a81..43e08b9dc 100644 --- a/artiq/language/core.py +++ b/artiq/language/core.py @@ -114,6 +114,7 @@ class int: __add__ = __iadd__ = _binaryop(host_int.__add__, "__radd__") __sub__ = __isub__ = _binaryop(host_int.__sub__, "__rsub__") __mul__ = __imul__ = _binaryop(host_int.__mul__, "__rmul__") + __truediv__ = __itruediv__ = _binaryop(host_int.__truediv__, "__rtruediv__") __floordiv__ = __ifloordiv__ = _binaryop(host_int.__floordiv__, "__rfloordiv__") __mod__ = __imod__ = _binaryop(host_int.__mod__, "__rmod__") __pow__ = __ipow__ = _binaryop(host_int.__pow__, "__rpow__") @@ -122,6 +123,7 @@ class int: __rsub__ = _binaryop(host_int.__rsub__, "__sub__") __rmul__ = _binaryop(host_int.__rmul__, "__mul__") __rfloordiv__ = _binaryop(host_int.__rfloordiv__, "__floordiv__") + __rtruediv__ = _binaryop(host_int.__rtruediv__, "__truediv__") __rmod__ = _binaryop(host_int.__rmod__, "__mod__") __rpow__ = _binaryop(host_int.__rpow__, "__pow__") From 60c985bf0be0d50708c3ed568b7c42e9794253dd Mon Sep 17 00:00:00 2001 From: whitequark Date: Sun, 27 Sep 2015 17:55:19 +0300 Subject: [PATCH 287/369] Remove debug print. --- artiq/compiler/transforms/iodelay_estimator.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/artiq/compiler/transforms/iodelay_estimator.py b/artiq/compiler/transforms/iodelay_estimator.py index eb203c5dd..afdbc37e0 100644 --- a/artiq/compiler/transforms/iodelay_estimator.py +++ b/artiq/compiler/transforms/iodelay_estimator.py @@ -243,8 +243,6 @@ class IODelayEstimator(algorithm.Visitor): for arg_name, arg_node in zip(typ.args, node.args[offset:]): args[arg_name] = arg_node - print(args) - free_vars = delay.length.free_vars() self.current_delay += delay.length.fold( { arg: self.evaluate(args[arg], abort=abort) for arg in free_vars }) From 7a6fc3983c6bafda23fb64de61bc8d73e5b8bf2b Mon Sep 17 00:00:00 2001 From: whitequark Date: Wed, 30 Sep 2015 18:41:14 +0300 Subject: [PATCH 288/369] Make delay component of function type unifyable. --- artiq/compiler/iodelay.py | 59 ---------- artiq/compiler/module.py | 3 +- artiq/compiler/testbench/signature.py | 4 +- .../compiler/transforms/iodelay_estimator.py | 41 +++---- artiq/compiler/types.py | 102 +++++++++++++++--- artiq/coredevice/dds.py | 1 + lit-test/test/iodelay/error_unify.py | 11 ++ lit-test/test/iodelay/return.py | 3 +- 8 files changed, 128 insertions(+), 96 deletions(-) create mode 100644 lit-test/test/iodelay/error_unify.py diff --git a/artiq/compiler/iodelay.py b/artiq/compiler/iodelay.py index 5cbb3bb37..165ce3950 100644 --- a/artiq/compiler/iodelay.py +++ b/artiq/compiler/iodelay.py @@ -4,9 +4,6 @@ the statically inferred RTIO delay arising from executing a function. """ -from pythonparser import diagnostic - - class Expr: def __add__(lhs, rhs): assert isinstance(rhs, Expr) @@ -227,59 +224,3 @@ def is_const(expr, value=None): def is_zero(expr): return is_const(expr, 0) - - -class Delay: - pass - -class Unknown(Delay): - """ - Unknown delay, that is, IO delay that we have not - tried to compute yet. - """ - - def __repr__(self): - return "{}.Unknown()".format(__name__) - -class Indeterminate(Delay): - """ - Indeterminate delay, that is, IO delay that can vary from - invocation to invocation. - - :ivar cause: (:class:`pythonparser.diagnostic.Diagnostic`) - reason for the delay not being inferred - """ - - def __init__(self, cause): - assert isinstance(cause, diagnostic.Diagnostic) - self.cause = cause - - def __repr__(self): - return "<{}.Indeterminate>".format(__name__) - -class Fixed(Delay): - """ - Fixed delay, that is, IO delay that is always the same - for every invocation. - - :ivar length: (int) delay in machine units - """ - - def __init__(self, length): - assert isinstance(length, Expr) - self.length = length - - def __repr__(self): - return "{}.Fixed({})".format(__name__, self.length) - -def is_unknown(delay): - return isinstance(delay, Unknown) - -def is_indeterminate(delay): - return isinstance(delay, Indeterminate) - -def is_fixed(delay, length=None): - if length is None: - return isinstance(delay, Fixed) - else: - return isinstance(delay, Fixed) and delay.length == length diff --git a/artiq/compiler/module.py b/artiq/compiler/module.py index 8c320f8c7..cc1f26032 100644 --- a/artiq/compiler/module.py +++ b/artiq/compiler/module.py @@ -50,7 +50,8 @@ class Module: inferencer = transforms.Inferencer(engine=self.engine) monomorphism_validator = validators.MonomorphismValidator(engine=self.engine) escape_validator = validators.EscapeValidator(engine=self.engine) - iodelay_estimator = transforms.IODelayEstimator(ref_period=ref_period) + iodelay_estimator = transforms.IODelayEstimator(engine=self.engine, + ref_period=ref_period) artiq_ir_generator = transforms.ARTIQIRGenerator(engine=self.engine, module_name=src.name, ref_period=ref_period) diff --git a/artiq/compiler/testbench/signature.py b/artiq/compiler/testbench/signature.py index d0139a32f..98d4687fb 100644 --- a/artiq/compiler/testbench/signature.py +++ b/artiq/compiler/testbench/signature.py @@ -32,8 +32,8 @@ def main(): if force_delays: for var in mod.globals: typ = mod.globals[var].find() - if types.is_function(typ) and iodelay.is_indeterminate(typ.delay): - process_diagnostic(typ.delay.cause) + if types.is_function(typ) and types.is_indeterminate_delay(typ.delay): + process_diagnostic(typ.delay.find().cause) print(repr(mod)) except: diff --git a/artiq/compiler/transforms/iodelay_estimator.py b/artiq/compiler/transforms/iodelay_estimator.py index afdbc37e0..733c93e41 100644 --- a/artiq/compiler/transforms/iodelay_estimator.py +++ b/artiq/compiler/transforms/iodelay_estimator.py @@ -11,7 +11,8 @@ class _UnknownDelay(Exception): pass class IODelayEstimator(algorithm.Visitor): - def __init__(self, ref_period): + def __init__(self, engine, ref_period): + self.engine = engine self.ref_period = ref_period self.changed = False self.current_delay = iodelay.Const(0) @@ -83,7 +84,7 @@ class IODelayEstimator(algorithm.Visitor): except (diagnostic.Error, _UnknownDelay): pass # we don't care; module-level code is never interleaved - def visit_function(self, args, body, typ): + 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) @@ -93,19 +94,23 @@ class IODelayEstimator(algorithm.Visitor): self.abort("only return statement at the end of the function " "can be interleaved", self.current_return.loc) - delay = iodelay.Fixed(self.current_delay.fold()) + delay = types.TFixedDelay(self.current_delay.fold()) except diagnostic.Error as error: - delay = iodelay.Indeterminate(error.diagnostic) + delay = types.TIndeterminateDelay(error.diagnostic) self.current_delay = old_delay self.current_return = old_return self.current_args = old_args - if iodelay.is_unknown(typ.delay) or iodelay.is_indeterminate(typ.delay): - typ.delay = delay - elif iodelay.is_fixed(typ.delay): - assert typ.delay.value == delay.value - else: - assert False + try: + typ.delay.unify(delay) + 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) @@ -116,10 +121,10 @@ class IODelayEstimator(algorithm.Visitor): body = node.body[:-1] else: body = node.body - self.visit_function(node.args, body, node.signature_type.find()) + self.visit_function(node.args, body, node.signature_type.find(), node.loc) def visit_LambdaT(self, node): - self.visit_function(node.args, node.body, node.type.find()) + self.visit_function(node.args, node.body, node.type.find(), node.loc) def get_iterable_length(self, node): def abort(notes): @@ -225,10 +230,10 @@ class IODelayEstimator(algorithm.Visitor): else: assert False - delay = typ.find().delay - if iodelay.is_unknown(delay): + delay = typ.find().delay.find() + if types.is_var(delay): raise _UnknownDelay() - elif iodelay.is_indeterminate(delay): + elif delay.is_indeterminate(): cause = delay.cause note = diagnostic.Diagnostic("note", "function called here", {}, @@ -236,15 +241,15 @@ class IODelayEstimator(algorithm.Visitor): diag = diagnostic.Diagnostic(cause.level, cause.reason, cause.arguments, cause.location, cause.highlights, cause.notes + [note]) raise diagnostic.Error(diag) - elif iodelay.is_fixed(delay): + 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(typ.args, node.args[offset:]): args[arg_name] = arg_node - free_vars = delay.length.free_vars() - self.current_delay += delay.length.fold( + free_vars = delay.duration.free_vars() + self.current_delay += delay.duration.fold( { arg: self.evaluate(args[arg], abort=abort) for arg in free_vars }) else: assert False diff --git a/artiq/compiler/types.py b/artiq/compiler/types.py index ea2d1d875..2490b866f 100644 --- a/artiq/compiler/types.py +++ b/artiq/compiler/types.py @@ -5,6 +5,7 @@ in :mod:`asttyped`. import string from collections import OrderedDict +from pythonparser import diagnostic from . import iodelay @@ -192,8 +193,8 @@ class TFunction(Type): optional arguments :ivar ret: (:class:`Type`) return type - :ivar delay: (:class:`iodelay.Delay`) - RTIO delay expression + :ivar delay: (:class:`Type`) + RTIO delay """ attributes = OrderedDict([ @@ -201,12 +202,12 @@ class TFunction(Type): ('__closure__', _TPointer()), ]) - def __init__(self, args, optargs, ret, delay=iodelay.Unknown()): + def __init__(self, args, optargs, ret): assert isinstance(args, OrderedDict) assert isinstance(optargs, OrderedDict) assert isinstance(ret, Type) - assert isinstance(delay, iodelay.Delay) - self.args, self.optargs, self.ret, self.delay = args, optargs, ret, delay + self.args, self.optargs, self.ret = args, optargs, ret + self.delay = TVar() def arity(self): return len(self.args) + len(self.optargs) @@ -222,6 +223,7 @@ class TFunction(Type): 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: @@ -261,7 +263,7 @@ class TRPCFunction(TFunction): def __init__(self, args, optargs, ret, service): super().__init__(args, optargs, ret, - delay=iodelay.Fixed(iodelay.Constant(0))) + delay=FixedDelay(iodelay.Constant(0))) self.service = service def unify(self, other): @@ -284,7 +286,7 @@ class TCFunction(TFunction): def __init__(self, args, ret, name): super().__init__(args, OrderedDict(), ret, - delay=iodelay.Fixed(iodelay.Constant(0))) + delay=FixedDelay(iodelay.Constant(0))) self.name = name def unify(self, other): @@ -418,6 +420,63 @@ class TValue(Type): def __ne__(self, other): return not (self == other) +class TDelay(Type): + """ + The type-level representation of IO delay. + """ + + def __init__(self, duration, cause): + 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 self.is_fixed() and other.is_fixed() and \ + self.duration.fold() == other.duration.fold(): + pass + elif isinstance(other, TVar): + other.unify(self) + else: + 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 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) @@ -511,6 +570,16 @@ def get_value(typ): 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 @@ -553,14 +622,10 @@ class TypePrinter(object): args += ["?%s:%s" % (arg, self.name(typ.optargs[arg])) for arg in typ.optargs] signature = "(%s)->%s" % (", ".join(args), self.name(typ.ret)) - if iodelay.is_unknown(typ.delay) or iodelay.is_fixed(typ.delay, 0): - pass - elif iodelay.is_fixed(typ.delay): - signature += " delay({} mu)".format(typ.delay.length) - elif iodelay.is_indeterminate(typ.delay): - signature += " delay(?)" - else: - assert False + delay = typ.delay.find() + if not (isinstance(delay, TVar) or + delay.is_fixed() and iodelay.is_zero(delay.duration)): + signature += " " + self.name(delay) if isinstance(typ, TRPCFunction): return "rpc({}) {}".format(typ.service, signature) @@ -580,5 +645,12 @@ class TypePrinter(object): return "".format(typ.name, attrs) 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/coredevice/dds.py b/artiq/coredevice/dds.py index 6620036d7..8350ee3d2 100644 --- a/artiq/coredevice/dds.py +++ b/artiq/coredevice/dds.py @@ -1,4 +1,5 @@ from artiq.language.core import * +from artiq.language.types import * from artiq.language.units import * diff --git a/lit-test/test/iodelay/error_unify.py b/lit-test/test/iodelay/error_unify.py new file mode 100644 index 000000000..723b5e393 --- /dev/null +++ b/lit-test/test/iodelay/error_unify.py @@ -0,0 +1,11 @@ +# 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/lit-test/test/iodelay/return.py b/lit-test/test/iodelay/return.py index 3a3dc09fa..64398dfc4 100644 --- a/lit-test/test/iodelay/return.py +++ b/lit-test/test/iodelay/return.py @@ -7,7 +7,8 @@ def f(): delay(1.5) return 10 -# CHECK-L: g: (x:float)->int(width=32) delay(0 mu) +# CHECK-L: g: (x:float)->int(width=32) +# CHECK-NOT-L: delay def g(x): if x > 1.0: return 1 From 651e6b1b7af9d2e269ac5012e45545854285994b Mon Sep 17 00:00:00 2001 From: whitequark Date: Wed, 30 Sep 2015 18:41:47 +0300 Subject: [PATCH 289/369] Remove build products from git. --- .gitignore | 2 +- lit-test/libartiq_support/libartiq_support.so | Bin 24096 -> 0 bytes 2 files changed, 1 insertion(+), 1 deletion(-) delete mode 100755 lit-test/libartiq_support/libartiq_support.so diff --git a/.gitignore b/.gitignore index b22661be4..2543a3849 100644 --- a/.gitignore +++ b/.gitignore @@ -14,4 +14,4 @@ doc/manual/_build /.coverage examples/master/results Output/ -/lit-test/libartiq_personality/libartiq_personality.so +/lit-test/libartiq_support/libartiq_support.so diff --git a/lit-test/libartiq_support/libartiq_support.so b/lit-test/libartiq_support/libartiq_support.so deleted file mode 100755 index baa5a31aeea4c783fb5e69d20d69684e847d942e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 24096 zcmd6Pdwf*Yz3fy&}z;7{?=n>&m`$R z_x^D{XD74Q`u*1LwSMcj)?RDxwfA0k!y22WF?HD4b&R0yEP<(!1?QV(21yND&eCz7 z!)9?>$7s=*+`eLgQYm8wN05$m{0_+S1H}Tb(z7;B#*WpARPhm-_>M@vBa%<0XJu7X zs!R4Xr1U!|%TrS+j;WMr>1cc_=BNDOxf~pm`URS)RPo&aKC<(_dgMt$;xEL{j^El1 z&Foi?FSxV)>xb_OTv7hQ!y|)-FKvJHuS81aQ}HwXc=o)VW!HS2aMSUdh2JXozr8c% z$}29O_|FIK{^7%KN6y``W#$IQ`S`N~@8!Pz(|2<}eD(L!9(wSAY-un|aM+oXccc zWuB2#dKaL4mc?Fh3A7t@bbO1`Pi4zH3=XT)RIWFRbF<1KqLCaL7cj2}fE3enB%TFd#&lyn$%ED?lQn0jQM{*9KzsjaG=P42L$6BTP}K zb{A?AgCWw|?m#3Orn<#;S(zJrq4v11J+P@e(AwG7*}B>n^O@zF0u?*}b-x*n4m@aoz~WoImS>-^!Y1z)6uAU$WnTd%9< zE%*!z{g4H(d`HC>EqFAHd0e*OlP-1A|0J8PtLo4#c&nfDEO^<3oTbQu&rw2<8Ww!6 z1z%>tTgUTC3*I{ZFSp?3(8i_JSn$?%^jPqdEqXnD@93U>$F`XU^Xxwva}1q8p{MV( z{(>MHSsFpnNJ$K5*X$bP2=1W5;qxOnN^T>VqJ8)*hg%4ykQhG6;ado%kR3k8;U zf~m=dD>|YeyJIrk{^oSycIUQ{^KGu#s~Ow& z0(rTu+%}Z17+_N3tys3Fzq$(ep@pd7kO!x2FFG25(B2 zl)`T4pCk1TlgN>?+qnNJ?@8=F=XuF}nb+qf_eGxeyU%(0nJ3{M^7P|3(5QJ5%|otd z*a7$NT+g_F*LQwz;`=DNm^k&*UnkCb4tbpJ%bq^>#SALlce3XU3L&C-$dkxJ_VTtD z{(E!@<7&`ZW_tQ|UtGBp4e7r496T|SWa_9%eedKYE+24z*t`3~k$A>&s{Vxl#C+Iy z-qm+Gd*|lFNzXvxe(sh5$8^M6f11tHdKN*G1Hhx59XWrTBK|_faq3tX%8pb2xiCi{ zrwe3(KxPZ1W2ouFk&zz=pY**`gElymxOl*Q+H8n^H#|rUaItsy>5;gTd^r3FIx;Lf zEi7~0zXEWQb;r;%OzR#6z^x+>2w|6bw(q1)f`@T&LWDY$H zJ8%I2k9+938ggdhMT|m^PQ~y*_3<3PVRKV0{X7Xr7qAG^?bOZ*M?0XG98cp+hvy~7 zQ#Z-%Ky@9kLvQ{Sodj{e`$I=;5sXR~MtKHueuxnn1~mwS>{f%EP&%{^oX>Ho152;x zQC0mOn1|jF5+Uok$uI>j8^}2)WYKyCmHRe9s&dbRd}ujNG*A!GmPSQxwL6%{7fNTyQr!w?sz!yDNBTb;o_OHhe zgL}W@R|UM9^$8e4L3^{%k#DW+TS7ccJitxl-JuzEK9%k{^{X_G_Od6jN{0dq1<6mL zprALaNtYx769-8o#(=)_c?gzuFlmD@3C8@8hQNWPk6J6d#WY6vi_{E6I=H%l>kSXC zYl&BfR&gqa-4u0?5OteXR1S!Ex0LO4&pQ{EQZVr8OY2>Mir)s!lhGW;nHkNp-mL#*kobOyO>fbPE8a#{s0__ zb^HVwOd&p&Pc5}_ z1pjL!O^pj*6|7`8Md%I!6Z^Li*e?=v;!J*N9X2pI_4Gd|3LSM;(Qoozu=GKz==WYLXa&MKv!9iw}({cwH3W>YKvxT~TmAilJ&>%|pZ8 z3vIo)F!rxMfZV~vOQ`B0RD6kxHxezYZmduIpjKUJPxIJa`m{A@E8nM3z?HUd_W>5a z3fzCr72E@pKO^YRSm_z)hd+t27a9+Fu63Vv-M@!Jq}hFt+(F3uIyutubyL_{A?zWm zuy0-(8F|V5M}95n+uh4t`-(BQq*`GvP8W_N+`ap0F@Po(iKAJ{)otRDiM~vOX)SupJ0b|I;EFpki1vS6I{kAjuY;mwWFisspYZSlXIK z&7!LGKPUt^hO&9(#Z|9QkUwrEfA=DCRJ zmH$cbBL*;?Vi)Gah2(n{w_UixZ1DZOIjCFnCLNxG`MTvQ;M)I1@Ip_uspsEG!NAfb zRy}K>M{&_rho-kfFV)bj5zG%3LG80V5XE?yc!fGz6PS3FFmFK%@EdM&z!U_FI6?C{ z5FPMnXd*e7@*ypT!sD=kU5NGV{v(SmfRTr?gg-G~5=K5jBe@!lzWgpY=)S{+RiF); zw3&kTkd<}^(ZZqZ!W5`&g#GAwkGbwmd?4|o(<)kcVZL?}6Du0D&h*z5t`;+kGyOd@ z-#Fvx@75`85c7AWHI)MIDHADO!^ENi!%A(_O*e`u`2mpt$fw-z4#9!1Ackk<_NsN^lF%V^j6=p`da z(~;)jM{w$|5V#rm&Bd<;zdiUphTp6BUBYh{#wqLX)4&w~w;aDM_?1Lg7}eF)*BSiQ zxltDKbp@(Rq6>_!Xgg24eGy+*wEEVPV9>w7JZ+ngp_Y!a2yU_mV~}BZo15yq?hUKG z8`pR@ZLY0bH{U2NHRd*l0zJ4n9t-#l+~^NS41XNA`rC~-zrl~(Tvie_+HkWwxWXuj zO1bK8z4b=F5$y=agMK3vjv0|aEFKAgQ<*RB{byP+boXD1V`ZJ8wqR#_N35Jbd?=6Y z>J9)EHs(|tmBP>s0hs6u83FaQ!H9*ShdYLBl$u(D0beNIEd?vF3$JVOwcZ|!_*w(r zD4u^9A2%oox0#A96!VRWe^7dBKx5jA{~0B`tcyt^Aukph0{gX=8q zC4*PgHW>B$pj$@7bq05jQD$`!x4a%v!0C8AljmA}!64cv7BI^A!ah+b`I8(VBBwP>RosLC zt}cliN@Qa!f`8I$!EhUiOW_b1q9VLv5F@Qb%*8`~)gOriqt0#dE^-U?H0f9Jm+2)L zmx@&~DJ+cxQmA@LCmsNz9wtIP6O@nfRDi2;)qT8XeDW|b`K^!rsH9nSH?jreR#{1~ zbAh2w^D})hU{{oM;y;sd0Kp@2)HiwcwHfwnw?h_|3hXn#8yUG9>Cc8oM!tfy=IxP@ z(@1}c^fyR1Ult8|oqs!n&S_ob`v^$%a z)|02r$j;DdZ%8q4^yoMbdTRi)+`KipMc2EscIv%s<@9T>T3kFASWdGVX&LMa5~4OY z?><{yPP(lfl0>;5=ordBomBoQvwR=$zeD*wN#%E&`?OUuV_R-HlD`s z1Js{Cb64fS(Lv?E!OwtSo+89JbF0V^c1^v z6p6%qR(qt>;26oq&PaL{k2F=KW2#7)M;fH?-7=%>JSXX}vf+o)hxs|Tzf#g;ro>^T z7t(OmmI{u0qe&Zd2elw^9ed?Z>R33_98~Z$@spv?iXoRFRj?%R^VTg7Rr+a`I&k&oI zUX0kVzmEX5m8>MuHb)I%Uckv#dIKcVt# zn&X4N@@yaBKcsw~egyN1Pi7sN}$7&SnA@ap0J9D*;v78Sor; zt|ZY*xNRq$JE>%ub{-l|vrIqL?%@ooP?6&ZRZ`C(f-g~)>7ODWeVIyi{S=XYjcS|6 z()9u0()D!IEsNTd%+9`u3}LA?IrgH$76AKa069dPo0dMa27+CmgC^z*1J7Jn;jFn9 zK%UK&NipjB1Tbl?Fllw&1@d&)dlWIQr@^H&`v8FSt3Ch`adEGRF`1Y3ra*b47Q z@MdI^gIEUb|EtN!p!=hf-ysqDCfJjaO{KbiH_jP3YkQU(` zMgAmnJr0>?nd?Ic?bTe*fNVr#Q~n**=&sR*Hlb9%2K3r6H3=$Cdfz6$8Tus@Y8NQ> zw(GMA{|=FNO#LP(wcnoszN3a^QAH`BizpEBJZu{68rG`F-)dA1B2R~Q_+=1I{|xb0 zQPwu-nbAy=7ZbT{uw%vvV6`Oz+CF0^%+;3iL3Hq@ncYlVc9wH(K^s#pU4ISdoZG-W zEr-;uAL3YIlaJ06Q1YeZ&d@jrBzlatUR_!FK?1RVmPVc5#>?C&k;J5Y) z=k!P`$pO2BCEI5mmY2*aAf1qx?;gKF^Lr#Q_P zHtV;NMs1y<8-g>^aninUgwr&#Ss5-NKuyVRA(}1JHeX%BX*RG~3nY!wwTEa5P%Z7~ z8cyS3vl}Fh(lz4}>3N#Ei*2w5A-4!Q?NaJaTJ7*VpqqXdFQlw(aLb(ci3y?Ow7)wC zSscPyBQz$E>(hDf=OZG~@xGYDa!BRe`$=!6mb#@@JA4f&x#)T>+T1wI#So5*aj_gC zyz+14?it#1lY{|O4?48NM^RdQA4S3pP1v)w1j|PPsmM0i$d!B(d8a*Zu3#&@o*1ss z=yoc)0xCdVl50xWX=s@D6B2$UmzXd8%vUB{=O-}%F5X2J74wUZZLpzuJwySdaT;B% z_#SGqV(kd%;qT%vk-idc5yf8da`H-v_FGWf2aoM7ewA9UlwaHIgOBYM_RUlF@fsY4 zQm5UJHNXufrY+QDH!A%x#eTU2x0F_rx(W$4ifa||bC*tMIaGo9tz^S0Ud7an%G%*_ zbZk+@I$lNl;Nd-@j&;0__Q6AYL?!EZCGCR;_lR1qmbE;vN7Qn)tYz;WUJVlLwAUmx z#0FW>Ev3iEre=+Lo_+Al9$w3Bsve@2+a>4`we(4_rgSFxpiL7U`g?mz|C5|>yM({H zxAbcyJIH0D%}Z$kuI-e?Pwp-K3AJaBM!l6c!8~e`UDA{zdrN6ns@*B!LwiftQTbgG zKDf8kOY-iP@X0;1R?6Xm26Z}Ua$ov4V!DHyOzqBQUyxLEIpb9GDUS9^iUWH~-=JFD zBjMh?rSFoNeGtmJG-Cl#<7^FUa!qdrEI3i@&VVz-=FV zV^8TnkpYiN`p5Q^K1=jpkrtob0|;G@rfV~Jue?b8&uKq7&)_9j^+KKgI8D)59ZqSi z1!uXYcm$@=nj&?vLbE0tP26oiTaL3`PVnXGKrRX7IzTQx#Ck(5GUUoa&FSr8HjkMf zq-E^lBKcatM(Y!MX$z^MGJ9DkWh0c`$+LG*_D-@w*VFW0B6Vh>f@#igQkG^(X_>vq zvP?cpO3PRdzi097GTAgsO3S8FJ)34pX*mxdpO;Ouq_kYh7iQBeDa}PRMcFhcTwcmGrCdll z=v4xny$aM$yL#7yX_K^)+=487?u^_?T7E_z`l+p$Wo2Cl6zVvI*>hK9U7J;30OKT{ z{OAG+?5Bi^G72<%ZdMjiY5?^dlWjT3oDy`@7Z8}Kgd(Cet-gRbvgfe+f(jJolp;fm z3vE=Ks|@01=AhvUkoovDG}2^k-ZbLNcX0B8sTT6WQSI<~4hTr=6mFtJnK*TnXgT7u zi0emYS~+S$MDwQA7m!D4iaxiBtD5dGD`mrAG-F8;I1`{QJVm8fj1}`)A!b$s91rsFW>ZBx z8mZ_EwFcvU3E=&q3cOBK(X)J+ciEDKK|Jp3S=b(mSKwA%GNoi2OWD#Hi?VXY#7kP1 z=O5*m{xN!A3-`pzJ76W{{ed>*F^JLQpD3~z(_lnrSWt*3jL4(+jTP9T1WIa*CN{OI zv@g>7`u5@0nf4@|^f~<8N9SAiJ+ZFp9~2Tz`Zv&TkDa$v=>>gH zw13*RxbWzSTi(C*{jEozII(r`ffHMg?mJO+^gAc2kgr01(YC+n>z3MgY_YqHz9*j` zf$Qv3%TL&DEz;w9!Rudq;w`SEE#0TmHPKodFA{L?U54N?2M0``ks;P!OOVew+ zZMPJ?%-vwSIzN374NVoaxpO#fOS;|pnk^HRUBxT=@d+!ddi7|3gKfed>iWWNJf#e` zwR!Q{V*N&MB;fOVyJHbaW$qR5;z4#7^R{;QI`RHw(z}=R5PB1e`Kz67yjv zCEmtp^8yK$FHP2oXZb9Sz7HKJ5w_5Lig3%85Q^^&5Kp7=f}?zA)2kl2S{{`1(Sjaj z<9RYS9xA;pzUb)dmg78^&JP%z~mvQpL8 zd=s^;6FUOJqpqiyGZn+A(zc+lJqq2m-ZdMW>fGMu4L8+qSZ&ssn7pm=h_^KyjCX~& zG2%^Bp^rb$_FEoKld~5sgR`l*xtf5#F$^SjZ+OE&KLrH6?P_`_POp(x$=Yvha@Vij zVBK?3+q~AZIfZ%&+u;i?!Yiocu?-uW*286+HgBYtW`WqajGz7t^7iDyg3!me066sT?wgaVPyR)DlU zr5cyHh0B&03)_u_=)={m*IZ*PY-_Bq;|}-KZdl#mZt|{as$K6E!4VHdv8@E#d?-Ln zZP+M6E=q=%aKm;+&^>ssN-kY4d||5C*i4NYrJloI+vWWTTbwNF;M4fxDmL0Lsf)=z z!*_dF3?NQiq!`cHe6=$?lgGa=s2KOq?84S1HPR$rcJDR3b zsFP#8JSq_sU13ZdR3EPu-N2;wwM1z+p+IYvVgsr(?ZDg6^geb)N4P6cv9kkSx+9K8 zthgzR6L0i|{=g2AtH4Xs3uA$344D=Y5ONpKsA;rQh)h#Gh!05(flzy_gO5ctyrp$goUCj5RKx+ZZ3+v$w$MK9d5jE zO?`vgI-t>LVYbzJzPraiT2Z_~P7%nW9i43m{_ZeF2NC{JjP>DeFCXRt)SvN6{0_W= z?pac$BQ-mm~9{C@w6j zFY$nomoqQC?Oy~QBnTQx7K;Sp2wI9{%n-)B@euYGZa7Qp!d7cMLd=_T917L0pN^ z0eKn62*;WHn6pTXoWPVvcXc62!m{;7*{8gW=T|KLS=B+N(c@<4~T4clD<-VS`C z!Gr_{;vnMNB{)?6wpNfqy05FV6?TLn5T|mkMM%Wg8(4X3xT_2MCqWu$iMN9pAB_kA zFF@`!Rn*cF3G7fM{1Pim;2D~#{-eW=<&^sVkCl$T`JndsB;)N&?e9s(r!ln;C>ihI z52CC%%G+5wQ~PkNWp2-*d8-+Oc~| z?UPEz=dhEsi6rH)vt0B0N7f=c!xOD5tr(HFEj9ZW+8sdl-@qJhDMFkM$c_Xd9>9al?Y+kKs0u^{AD2b3>gh zya}m>U1-WbSY7d>jpN6*;{foaw}uR}91kI{jS~;Yfgi74bT66PFXaTKtaB2d`gHp@ z5}*1!n(h#hoYd#e8K?-=OFhfzt^pyAJdV#{>TxRW|MFuN@FXYoDQCIFr#^|SmvYpz z(>y8XCW%*%I~6`4@#-O>ikPUxtEX~>QqDcVkLR~fPk;w{T>2wizKiu1OTjXEg5$@= z6%~)C_c=~Kwtv0E@nie%SxKMzou@ZAer*4xZDaY#CUVGu+iS)+_&LCjM_)Su-Uoah z8~a@=`f^GBIOAl9T>Bl;>0r))hm(=fg-5~L)?MR#A#`8mL0(=7a@$C5m@Z)`#>q`^Jp+{5W z$@vkdAN&2VUrr$BPr$3bY4yg3z|XZJWWEUZbI1D**({D9%dix9xnUx?ur(5k#^P;l zm<4*h*Vi?98|pW0#%-L@?~Hk4UAR{i4q;}bJ}_oMdA=nc@dds9SU3{(`r^8E|z zikR|h(LLURoUdF)Z_${`EOi^JylP1m?zBKBeG*NrW)(cnT`&vd;!3if-wlJ9#k5v2 zp(P~LgqAH|+q(8;$AmY<<$)<>p!X@LB3Yak7XWcBiqD=L=P$qdP(%KtY+^APOvz}!b1YTcyL zlxYLz2FVA_H}TRu-)KuEYJH{B21$-#l^=Mno^&h+L{n9zU#-JbS|k}sFVQG|HFK{4 zjz|>0TCb_}M;Zz(M+(S1D5G~K6u){;LZz!Dud-j!sk9m8^bUrC)%s7RHIhHoeu?Ja zl~rYgQ|m;P(tMZ>r9ZX)A(RonvQxcBqS7<6!o*KY$5j3v;D`jTtpS0vS(ikzgq9A6ua$`4)T@t_$L6Q zU$viF7pwH=cwlQiQuRN8LMwmjcZB^`X5>@#A4%ai*V$JJ#(gRNQ|%{88G9TVTEnRL zQ0sB^-Q`sJRQMV2(tcINuh#7ci-E+EN}mdUA2ej5;#ceYL;pqom%vN0r}))7Z>I38`wxxt zlIfKg#i!ukAVWA+f3*(ZBKeQkC`ys0fcR*ksDElb6@Tg-2o+8$y(IaC^wD!n>hB#z z@topQXxf#LileeWO8!Z5Gqg#sGBVAXfZy>kK|u3;>yfH!#svJwR|u|h6*y8zD*rsm zpX$HgUMCoyPiZj4uTmPGQcUEGkFFK`Rf Date: Sun, 4 Oct 2015 02:11:17 +0300 Subject: [PATCH 290/369] Highlight source range in IR dumps using colors. --- artiq/compiler/ir.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/artiq/compiler/ir.py b/artiq/compiler/ir.py index f20357ed8..c6b00869c 100644 --- a/artiq/compiler/ir.py +++ b/artiq/compiler/ir.py @@ -365,9 +365,9 @@ class BasicBlock(NamedValue): source_lines = loc.source_lines() beg_col, end_col = loc.column(), loc.end().column() source_lines[-1] = \ - source_lines[-1][:end_col] + "`" + source_lines[-1][end_col:] + source_lines[-1][:end_col] + "\x1b[0m" + source_lines[-1][end_col:] source_lines[0] = \ - source_lines[0][:beg_col] + "`" + source_lines[0][beg_col:] + 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")) From 42b0089a4c768992d291ecab45c0a8ff4ef3c094 Mon Sep 17 00:00:00 2001 From: whitequark Date: Wed, 7 Oct 2015 22:03:24 +0300 Subject: [PATCH 291/369] transforms.asttyped_rewriter: simplify. --- artiq/compiler/transforms/asttyped_rewriter.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/artiq/compiler/transforms/asttyped_rewriter.py b/artiq/compiler/transforms/asttyped_rewriter.py index ee93e0834..20aecdb62 100644 --- a/artiq/compiler/transforms/asttyped_rewriter.py +++ b/artiq/compiler/transforms/asttyped_rewriter.py @@ -38,8 +38,7 @@ class LocalExtractor(algorithm.Visitor): def visit_Assign(self, node): self.visit(node.value) - for target in node.targets: - self.visit_in_assign(target, in_assign=True) + self.visit_in_assign(node.targets, in_assign=True) def visit_For(self, node): self.visit(node.iter) @@ -49,14 +48,12 @@ class LocalExtractor(algorithm.Visitor): def visit_withitem(self, node): self.visit(node.context_expr) - if node.optional_vars is not None: - self.visit_in_assign(node.optional_vars, in_assign=True) + 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) - for if_ in node.ifs: - self.visit(node.ifs) + self.visit(node.ifs) def visit_generator(self, node): if self.in_root: From 6ac82e14399eba0e38fda9455f769cf10540ed99 Mon Sep 17 00:00:00 2001 From: whitequark Date: Wed, 7 Oct 2015 22:21:29 +0300 Subject: [PATCH 292/369] transforms.devirtualizer.FunctionResolver: implement. --- artiq/compiler/module.py | 4 + artiq/compiler/transforms/__init__.py | 1 + artiq/compiler/transforms/devirtualizer.py | 92 ++++++++++++++++++++++ 3 files changed, 97 insertions(+) create mode 100644 artiq/compiler/transforms/devirtualizer.py diff --git a/artiq/compiler/module.py b/artiq/compiler/module.py index cc1f26032..a7f773225 100644 --- a/artiq/compiler/module.py +++ b/artiq/compiler/module.py @@ -57,6 +57,7 @@ class Module: ref_period=ref_period) dead_code_eliminator = transforms.DeadCodeEliminator(engine=self.engine) local_access_validator = validators.LocalAccessValidator(engine=self.engine) + devirtualizer = transforms.Devirtualizer() self.name = src.name self.globals = src.globals @@ -65,9 +66,12 @@ class Module: monomorphism_validator.visit(src.typedtree) escape_validator.visit(src.typedtree) iodelay_estimator.visit_fixpoint(src.typedtree) + devirtualizer.visit(src.typedtree) self.artiq_ir = artiq_ir_generator.visit(src.typedtree) dead_code_eliminator.process(self.artiq_ir) local_access_validator.process(self.artiq_ir) + # for f in self.artiq_ir: + # print(f) def build_llvm_ir(self, target): """Compile the module to LLVM IR for the specified target.""" diff --git a/artiq/compiler/transforms/__init__.py b/artiq/compiler/transforms/__init__.py index 39831fea3..fed1c6648 100644 --- a/artiq/compiler/transforms/__init__.py +++ b/artiq/compiler/transforms/__init__.py @@ -3,5 +3,6 @@ from .inferencer import Inferencer from .int_monomorphizer import IntMonomorphizer from .iodelay_estimator import IODelayEstimator from .artiq_ir_generator import ARTIQIRGenerator +from .devirtualizer import Devirtualizer from .dead_code_eliminator import DeadCodeEliminator from .llvm_ir_generator import LLVMIRGenerator diff --git a/artiq/compiler/transforms/devirtualizer.py b/artiq/compiler/transforms/devirtualizer.py new file mode 100644 index 000000000..71000e36d --- /dev/null +++ b/artiq/compiler/transforms/devirtualizer.py @@ -0,0 +1,92 @@ +""" +: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 +(constructor type, field name) pair. +""" + +from pythonparser import algorithm +from .. import 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.in_assign = False + self.scope_map = dict() + self.scope = None + self.queue = [] + + def finalize(self): + for thunk in self.queue: + thunk() + + def visit_scope(self, node): + old_scope, self.scope = self.scope, node + self.generic_visit(node) + self.scope = old_scope + + 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_For(self, node): + self.visit(node.iter) + self.visit_in_assign(node.target) + 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) + + 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.scope, 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.scope, node.id), None) + else: + # Copy the final value in scope_map into variable_map. + key = (self.scope, node.id) + def thunk(): + if key in self.scope_map: + self.variable_map[node] = self.scope_map[key] + self.queue.append(thunk) + +class Devirtualizer: + def __init__(self): + self.variable_map = dict() + self.method_map = dict() + + def visit(self, node): + resolver = FunctionResolver(self.variable_map) + resolver.visit(node) + resolver.finalize() + # print(self.variable_map) From 962dd9de13aa1b505f748fa5d3d82076fc9be41e Mon Sep 17 00:00:00 2001 From: whitequark Date: Wed, 7 Oct 2015 22:32:46 +0300 Subject: [PATCH 293/369] transforms.devirtualizer.MethodResolver: implement. --- artiq/compiler/transforms/devirtualizer.py | 37 ++++++++++++++++++---- 1 file changed, 31 insertions(+), 6 deletions(-) diff --git a/artiq/compiler/transforms/devirtualizer.py b/artiq/compiler/transforms/devirtualizer.py index 71000e36d..08a624212 100644 --- a/artiq/compiler/transforms/devirtualizer.py +++ b/artiq/compiler/transforms/devirtualizer.py @@ -6,11 +6,11 @@ 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 -(constructor type, field name) pair. +(instance type, field name) pair. """ from pythonparser import algorithm -from .. import ir, types +from .. import asttyped, ir, types def _advance(target_map, key, value): if key not in target_map: @@ -80,13 +80,38 @@ class FunctionResolver(algorithm.Visitor): self.variable_map[node] = self.scope_map[key] 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 Devirtualizer: def __init__(self): self.variable_map = dict() self.method_map = dict() def visit(self, node): - resolver = FunctionResolver(self.variable_map) - resolver.visit(node) - resolver.finalize() - # print(self.variable_map) + 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) + print(self.method_map) From 7043b333a772cd4fff94485068fee9058441584d Mon Sep 17 00:00:00 2001 From: whitequark Date: Fri, 9 Oct 2015 00:29:31 +0300 Subject: [PATCH 294/369] =?UTF-8?q?transforms.devirtualizer=20=E2=86=92=20?= =?UTF-8?q?analyses.devirtualization.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- artiq/compiler/analyses/__init__.py | 1 + .../devirtualizer.py => analyses/devirtualization.py} | 3 +-- artiq/compiler/module.py | 6 +++--- artiq/compiler/transforms/__init__.py | 1 - 4 files changed, 5 insertions(+), 6 deletions(-) rename artiq/compiler/{transforms/devirtualizer.py => analyses/devirtualization.py} (98%) diff --git a/artiq/compiler/analyses/__init__.py b/artiq/compiler/analyses/__init__.py index f7f69f651..708b325ef 100644 --- a/artiq/compiler/analyses/__init__.py +++ b/artiq/compiler/analyses/__init__.py @@ -1 +1,2 @@ from .domination import DominatorTree +from .devirtualization import Devirtualization diff --git a/artiq/compiler/transforms/devirtualizer.py b/artiq/compiler/analyses/devirtualization.py similarity index 98% rename from artiq/compiler/transforms/devirtualizer.py rename to artiq/compiler/analyses/devirtualization.py index 08a624212..63eea451a 100644 --- a/artiq/compiler/transforms/devirtualizer.py +++ b/artiq/compiler/analyses/devirtualization.py @@ -102,7 +102,7 @@ class MethodResolver(algorithm.Visitor): continue _advance(self.method_map, (instance_type, target.attr), value) -class Devirtualizer: +class Devirtualization: def __init__(self): self.variable_map = dict() self.method_map = dict() @@ -114,4 +114,3 @@ class Devirtualizer: method_resolver = MethodResolver(self.variable_map, self.method_map) method_resolver.visit(node) - print(self.method_map) diff --git a/artiq/compiler/module.py b/artiq/compiler/module.py index a7f773225..0dff76786 100644 --- a/artiq/compiler/module.py +++ b/artiq/compiler/module.py @@ -10,7 +10,7 @@ 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, validators +from . import prelude, types, transforms, analyses, validators class Source: def __init__(self, source_buffer, engine=None): @@ -57,7 +57,7 @@ class Module: ref_period=ref_period) dead_code_eliminator = transforms.DeadCodeEliminator(engine=self.engine) local_access_validator = validators.LocalAccessValidator(engine=self.engine) - devirtualizer = transforms.Devirtualizer() + devirtualization = analyses.Devirtualization() self.name = src.name self.globals = src.globals @@ -66,7 +66,7 @@ class Module: monomorphism_validator.visit(src.typedtree) escape_validator.visit(src.typedtree) iodelay_estimator.visit_fixpoint(src.typedtree) - devirtualizer.visit(src.typedtree) + devirtualization.visit(src.typedtree) self.artiq_ir = artiq_ir_generator.visit(src.typedtree) dead_code_eliminator.process(self.artiq_ir) local_access_validator.process(self.artiq_ir) diff --git a/artiq/compiler/transforms/__init__.py b/artiq/compiler/transforms/__init__.py index fed1c6648..39831fea3 100644 --- a/artiq/compiler/transforms/__init__.py +++ b/artiq/compiler/transforms/__init__.py @@ -3,6 +3,5 @@ from .inferencer import Inferencer from .int_monomorphizer import IntMonomorphizer from .iodelay_estimator import IODelayEstimator from .artiq_ir_generator import ARTIQIRGenerator -from .devirtualizer import Devirtualizer from .dead_code_eliminator import DeadCodeEliminator from .llvm_ir_generator import LLVMIRGenerator From 7bcba52d6ad853cd5c33fe97202d1d5da66c4942 Mon Sep 17 00:00:00 2001 From: whitequark Date: Fri, 9 Oct 2015 00:53:14 +0300 Subject: [PATCH 295/369] compiler.embedding: fix loc. --- artiq/compiler/embedding.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/artiq/compiler/embedding.py b/artiq/compiler/embedding.py index 89bdf8f51..a2cf1aa34 100644 --- a/artiq/compiler/embedding.py +++ b/artiq/compiler/embedding.py @@ -94,9 +94,13 @@ class ASTSynthesizer: begin_loc=begin_loc, end_loc=end_loc, loc=begin_loc.join(end_loc)) elif inspect.isfunction(value) or inspect.ismethod(value): + quote_loc = self._add('`') + repr_loc = self._add(repr(value)) + unquote_loc = self._add('`') + loc = quote_loc.join(unquote_loc) + function_name, function_type = self.quote_function(value, self.expanded_from) - return asttyped.NameT(id=function_name, ctx=None, type=function_type, - loc=self._add(repr(value))) + return asttyped.NameT(id=function_name, ctx=None, type=function_type, loc=loc) else: quote_loc = self._add('`') repr_loc = self._add(repr(value)) From 844d37ff1837f8d4175b920fa6bcf2740d1b2836 Mon Sep 17 00:00:00 2001 From: whitequark Date: Fri, 9 Oct 2015 01:24:44 +0300 Subject: [PATCH 296/369] compiler.testbench.embedding: allow compiling only. --- artiq/compiler/testbench/embedding.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/artiq/compiler/testbench/embedding.py b/artiq/compiler/testbench/embedding.py index 7df21af21..8817711c0 100644 --- a/artiq/compiler/testbench/embedding.py +++ b/artiq/compiler/testbench/embedding.py @@ -6,6 +6,12 @@ from artiq.master.worker_db import DeviceManager from artiq.coredevice.core import Core, CompileError def main(): + if len(sys.argv) > 1 and sys.argv[1] == "+compile": + del sys.argv[1] + compile_only = True + else: + compile_only = False + with open(sys.argv[1]) as f: testcase_code = compile(f.read(), f.name, "exec") testcase_vars = {'__name__': 'testbench'} @@ -15,9 +21,12 @@ def main(): try: core = Core(dmgr=DeviceManager(FlatFileDB(ddb_path))) - core.run(testcase_vars["entrypoint"], (), {}) - print(core.comm.get_log()) - core.comm.clear_log() + if compile_only: + core.compile(testcase_vars["entrypoint"], (), {}) + else: + core.run(testcase_vars["entrypoint"], (), {}) + print(core.comm.get_log()) + core.comm.clear_log() except CompileError as error: print("\n".join(error.__cause__.diagnostic.render(only_line=True))) From 6922bd56383fff148483ed7359a4306242c1da86 Mon Sep 17 00:00:00 2001 From: whitequark Date: Fri, 9 Oct 2015 01:32:05 +0300 Subject: [PATCH 297/369] analyses.devirtualization: look up functions in outer scopes. --- artiq/compiler/analyses/devirtualization.py | 23 ++++++++++++--------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/artiq/compiler/analyses/devirtualization.py b/artiq/compiler/analyses/devirtualization.py index 63eea451a..14f93c882 100644 --- a/artiq/compiler/analyses/devirtualization.py +++ b/artiq/compiler/analyses/devirtualization.py @@ -22,19 +22,20 @@ class FunctionResolver(algorithm.Visitor): def __init__(self, variable_map): self.variable_map = variable_map - self.in_assign = False self.scope_map = dict() - self.scope = None self.queue = [] + self.in_assign = False + self.current_scopes = [] + def finalize(self): for thunk in self.queue: thunk() def visit_scope(self, node): - old_scope, self.scope = self.scope, node + self.current_scopes.append(node) self.generic_visit(node) - self.scope = old_scope + self.current_scopes.pop() def visit_in_assign(self, node): self.in_assign = True @@ -64,20 +65,22 @@ class FunctionResolver(algorithm.Visitor): self.visit_scope(node) def visit_FunctionDefT(self, node): - _advance(self.scope_map, (self.scope, node.name), 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.scope, node.id), None) + _advance(self.scope_map, (self.current_scopes[-1], node.id), None) else: - # Copy the final value in scope_map into variable_map. - key = (self.scope, node.id) + # 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(): - if key in self.scope_map: - self.variable_map[node] = self.scope_map[key] + 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): From 0bb793199f2a8641bcbd0a2ece1fbe1cb707b381 Mon Sep 17 00:00:00 2001 From: whitequark Date: Fri, 9 Oct 2015 01:32:27 +0300 Subject: [PATCH 298/369] transforms.artiq_ir_generator: devirtualize closure calls. --- artiq/compiler/ir.py | 28 +++++++++++++--- artiq/compiler/module.py | 3 +- .../compiler/transforms/artiq_ir_generator.py | 32 ++++++++++++++++++- 3 files changed, 56 insertions(+), 7 deletions(-) diff --git a/artiq/compiler/ir.py b/artiq/compiler/ir.py index c6b00869c..292d42427 100644 --- a/artiq/compiler/ir.py +++ b/artiq/compiler/ir.py @@ -48,7 +48,7 @@ class Value: self.uses, self.type = set(), typ.find() def replace_all_uses_with(self, value): - for user in self.uses: + for user in set(self.uses): user.replace_uses_of(self, value) class Constant(Value): @@ -126,11 +126,11 @@ class User(NamedValue): self.set_operands([]) def replace_uses_of(self, value, replacement): - assert value in operands + assert value in self.operands - for index, operand in enumerate(operands): + for index, operand in enumerate(self.operands): if operand == value: - operands[index] = replacement + self.operands[index] = replacement value.uses.remove(self) replacement.uses.add(self) @@ -851,6 +851,9 @@ class Closure(Instruction): class Call(Instruction): """ A function call operation. + + :ivar static_target_function: (:class:`Function` or None) + statically resolved callee """ """ @@ -861,6 +864,7 @@ class Call(Instruction): assert isinstance(func, Value) for arg in args: assert isinstance(arg, Value) super().__init__([func] + args, func.type.ret, name) + self.static_target_function = None def opcode(self): return "call" @@ -871,6 +875,12 @@ class Call(Instruction): def arguments(self): return self.operands[1:] + def __str__(self): + result = super().__str__() + 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. @@ -1080,6 +1090,9 @@ class Reraise(Terminator): class Invoke(Terminator): """ A function call operation that supports exception handling. + + :ivar static_target_function: (:class:`Function` or None) + statically resolved callee """ """ @@ -1094,6 +1107,7 @@ class Invoke(Terminator): assert isinstance(normal, BasicBlock) assert isinstance(exn, BasicBlock) super().__init__([func] + args + [normal, exn], func.type.ret, name) + self.static_target_function = None def opcode(self): return "invoke" @@ -1116,6 +1130,12 @@ class Invoke(Terminator): self.operands[-1].as_operand()) return result + def __str__(self): + result = super().__str__() + 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 diff --git a/artiq/compiler/module.py b/artiq/compiler/module.py index 0dff76786..0e607fa4d 100644 --- a/artiq/compiler/module.py +++ b/artiq/compiler/module.py @@ -68,10 +68,9 @@ class Module: iodelay_estimator.visit_fixpoint(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) local_access_validator.process(self.artiq_ir) - # for f in self.artiq_ir: - # print(f) def build_llvm_ir(self, target): """Compile the module to LLVM IR for the specified target.""" diff --git a/artiq/compiler/transforms/artiq_ir_generator.py b/artiq/compiler/transforms/artiq_ir_generator.py index 11ca96d52..3daadd8e2 100644 --- a/artiq/compiler/transforms/artiq_ir_generator.py +++ b/artiq/compiler/transforms/artiq_ir_generator.py @@ -63,6 +63,18 @@ class ARTIQIRGenerator(algorithm.Visitor): 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 + + 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 """ _size_type = builtins.TInt(types.TValue(32)) @@ -86,6 +98,19 @@ class ARTIQIRGenerator(algorithm.Visitor): self.continue_target = None self.return_target = None self.unwind_target = None + self.function_map = dict() + self.variable_map = dict() + self.method_map = dict() + + def annotate_calls(self, devirtualization): + for var_node in devirtualization.variable_map: + callee_node = devirtualization.variable_map[var_node] + 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 def add_block(self, name=""): block = ir.BasicBlock([], name) @@ -204,6 +229,9 @@ class ARTIQIRGenerator(algorithm.Visitor): 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() old_block, self.current_block = self.current_block, entry @@ -701,7 +729,9 @@ class ARTIQIRGenerator(algorithm.Visitor): def visit_NameT(self, node): if self.current_assign is None: - return self._get_local(node.id) + insn = self._get_local(node.id) + self.variable_map[node] = insn + return insn else: return self._set_local(node.id, self.current_assign) From b6c8c9f480938886d825b098e1ce81b84c9d8c12 Mon Sep 17 00:00:00 2001 From: whitequark Date: Fri, 9 Oct 2015 01:37:28 +0300 Subject: [PATCH 299/369] transforms.artiq_ir_generator: add tests for devirtualization. --- lit-test/test/devirtualization/ddb.pyon | 8 ++++++++ lit-test/test/devirtualization/function.py | 22 ++++++++++++++++++++++ 2 files changed, 30 insertions(+) create mode 100644 lit-test/test/devirtualization/ddb.pyon create mode 100644 lit-test/test/devirtualization/function.py diff --git a/lit-test/test/devirtualization/ddb.pyon b/lit-test/test/devirtualization/ddb.pyon new file mode 100644 index 000000000..7c1bb62ef --- /dev/null +++ b/lit-test/test/devirtualization/ddb.pyon @@ -0,0 +1,8 @@ +{ + "comm": { + "type": "local", + "module": "artiq.coredevice.comm_dummy", + "class": "Comm", + "arguments": {} + } +} diff --git a/lit-test/test/devirtualization/function.py b/lit-test/test/devirtualization/function.py new file mode 100644 index 000000000..5c24b492b --- /dev/null +++ b/lit-test/test/devirtualization/function.py @@ -0,0 +1,22 @@ +# RUN: env ARTIQ_DUMP_IR=1 %python -m artiq.compiler.testbench.embedding +compile %s 2>%t +# RUN: OutputCheck %s --file-to-check=%t + +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() From 48f1f48f096d965f5e432043244c099899b2a33b Mon Sep 17 00:00:00 2001 From: whitequark Date: Fri, 9 Oct 2015 02:27:52 +0300 Subject: [PATCH 300/369] transforms.artiq_ir_generator: devirtualize method calls. --- .../compiler/transforms/artiq_ir_generator.py | 27 ++++++++++++++----- lit-test/test/devirtualization/method.py | 16 +++++++++++ 2 files changed, 37 insertions(+), 6 deletions(-) create mode 100644 lit-test/test/devirtualization/method.py diff --git a/artiq/compiler/transforms/artiq_ir_generator.py b/artiq/compiler/transforms/artiq_ir_generator.py index 3daadd8e2..1bd1b01e6 100644 --- a/artiq/compiler/transforms/artiq_ir_generator.py +++ b/artiq/compiler/transforms/artiq_ir_generator.py @@ -6,9 +6,9 @@ but without too much detail, such as exposing the reference/value semantics explicitly. """ -from collections import OrderedDict +from collections import OrderedDict, defaultdict from pythonparser import algorithm, diagnostic, ast -from .. import types, builtins, ir +from .. import types, builtins, asttyped, ir def _readable_name(insn): if isinstance(insn, ir.Constant): @@ -100,18 +100,27 @@ class ARTIQIRGenerator(algorithm.Visitor): self.unwind_target = None self.function_map = dict() self.variable_map = dict() - self.method_map = dict() + self.method_map = defaultdict(lambda: []) def annotate_calls(self, devirtualization): for var_node in devirtualization.variable_map: callee_node = devirtualization.variable_map[var_node] 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] + 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) @@ -1553,12 +1562,18 @@ class ARTIQIRGenerator(algorithm.Visitor): assert None not in args if self.unwind_target is None: - return self.append(ir.Call(func, args)) + insn = self.append(ir.Call(func, args)) else: after_invoke = self.add_block() - invoke = self.append(ir.Invoke(func, args, after_invoke, self.unwind_target)) + insn = self.append(ir.Invoke(func, args, after_invoke, self.unwind_target)) self.current_block = after_invoke - return invoke + + method_key = None + if isinstance(node.func, asttyped.AttributeT): + attr_node = node.func + self.method_map[(attr_node.value.type, attr_node.attr)].append(insn) + + return insn def visit_QuoteT(self, node): return self.append(ir.Quote(node.value, node.type)) diff --git a/lit-test/test/devirtualization/method.py b/lit-test/test/devirtualization/method.py new file mode 100644 index 000000000..2105487e3 --- /dev/null +++ b/lit-test/test/devirtualization/method.py @@ -0,0 +1,16 @@ +# RUN: env ARTIQ_DUMP_IR=1 %python -m artiq.compiler.testbench.embedding +compile %s 2>%t +# RUN: OutputCheck %s --file-to-check=%t + +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() From 32ce33a1f9c8ebddecf627f0fe853235e2555c5d Mon Sep 17 00:00:00 2001 From: whitequark Date: Fri, 9 Oct 2015 03:10:39 +0300 Subject: [PATCH 301/369] transforms.artiq_ir_generator: emit ir.Parallel for with parallel:. --- artiq/compiler/ir.py | 20 ++++++++++++ .../compiler/transforms/artiq_ir_generator.py | 31 ++++++++++++++++++- 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/artiq/compiler/ir.py b/artiq/compiler/ir.py index 292d42427..cae1985d4 100644 --- a/artiq/compiler/ir.py +++ b/artiq/compiler/ir.py @@ -1000,6 +1000,7 @@ class IndirectBranch(Terminator): return self.operands[1:] def add_destination(self, destination): + destination.uses.add(self) self.operands.append(destination) def _operands_as_string(self): @@ -1176,3 +1177,22 @@ class LandingPad(Terminator): else: table.append("{} => {}".format(types.TypePrinter().name(typ), target.as_operand())) return "cleanup {}, [{}]".format(self.cleanup().as_operand(), ", ".join(table)) + +class Parallel(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 "parallel" + + def destinations(self): + return self.operands + + def add_destination(self, destination): + destination.uses.add(self) + self.operands.append(destination) diff --git a/artiq/compiler/transforms/artiq_ir_generator.py b/artiq/compiler/transforms/artiq_ir_generator.py index 1bd1b01e6..e03a732bc 100644 --- a/artiq/compiler/transforms/artiq_ir_generator.py +++ b/artiq/compiler/transforms/artiq_ir_generator.py @@ -669,7 +669,36 @@ class ARTIQIRGenerator(algorithm.Visitor): if not post_handler.is_terminated(): post_handler.append(ir.Branch(tail)) - # TODO: With + def visit_With(self, node): + if len(node.items) != 1: + diag = diagnostic.Diagnostic("fatal", + "only one expression per 'with' statement is supported", + {"type": types.TypePrinter().name(typ)}, + node.context_expr.loc) + self.engine.process(diag) + + 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) + elif types.is_builtin(context_expr_node.type, "parallel"): + parallel = self.append(ir.Parallel()) + + heads, tails = [], [] + for stmt in node.body: + self.current_block = self.add_block() + heads.append(self.current_block) + self.visit(stmt) + tails.append(self.current_block) + + for head in heads: + parallel.add_destination(head) + + self.current_block = self.add_block() + for tail in tails: + if not tail.is_terminated(): + tail.append(ir.Branch(self.current_block)) # Expression visitors # These visitors return a node in addition to mutating From 5d64df829eedb3c8287660354d062e6212cad728 Mon Sep 17 00:00:00 2001 From: whitequark Date: Wed, 14 Oct 2015 16:12:00 +0300 Subject: [PATCH 302/369] transforms.artiq_ir_generator: fix devirtualized call annotation. --- artiq/compiler/transforms/artiq_ir_generator.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/artiq/compiler/transforms/artiq_ir_generator.py b/artiq/compiler/transforms/artiq_ir_generator.py index e03a732bc..b39e464d6 100644 --- a/artiq/compiler/transforms/artiq_ir_generator.py +++ b/artiq/compiler/transforms/artiq_ir_generator.py @@ -105,7 +105,9 @@ class ARTIQIRGenerator(algorithm.Visitor): def annotate_calls(self, devirtualization): for var_node in devirtualization.variable_map: callee_node = devirtualization.variable_map[var_node] - callee = self.function_map[callee_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: @@ -115,7 +117,9 @@ class ARTIQIRGenerator(algorithm.Visitor): for type_and_method in devirtualization.method_map: callee_node = devirtualization.method_map[type_and_method] - callee = self.function_map[callee_node] + 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)) From b91ffa1b384817ef1db98194bc55a088ca0472f9 Mon Sep 17 00:00:00 2001 From: whitequark Date: Wed, 14 Oct 2015 17:02:59 +0300 Subject: [PATCH 303/369] ir: fix default argument fiasco. --- artiq/compiler/ir.py | 2 +- artiq/compiler/transforms/artiq_ir_generator.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/artiq/compiler/ir.py b/artiq/compiler/ir.py index cae1985d4..bf487960d 100644 --- a/artiq/compiler/ir.py +++ b/artiq/compiler/ir.py @@ -1184,7 +1184,7 @@ class Parallel(Terminator): in parallel. """ - def __init__(self, destinations=[], name=""): + def __init__(self, destinations, name=""): super().__init__(destinations, builtins.TNone(), name) def opcode(self): diff --git a/artiq/compiler/transforms/artiq_ir_generator.py b/artiq/compiler/transforms/artiq_ir_generator.py index b39e464d6..f73295d20 100644 --- a/artiq/compiler/transforms/artiq_ir_generator.py +++ b/artiq/compiler/transforms/artiq_ir_generator.py @@ -687,7 +687,7 @@ class ARTIQIRGenerator(algorithm.Visitor): if types.is_builtin(context_expr_node.type, "sequential"): self.visit(node.body) elif types.is_builtin(context_expr_node.type, "parallel"): - parallel = self.append(ir.Parallel()) + parallel = self.append(ir.Parallel([])) heads, tails = [], [] for stmt in node.body: From 3a1b77ae6ba87287f8665f82c06c1d99c62966d0 Mon Sep 17 00:00:00 2001 From: whitequark Date: Sun, 1 Nov 2015 09:49:39 +0300 Subject: [PATCH 304/369] analyses.domination: add PostDominatorTree. --- artiq/compiler/analyses/domination.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/artiq/compiler/analyses/domination.py b/artiq/compiler/analyses/domination.py index 7fbd4d231..50aacc52d 100644 --- a/artiq/compiler/analyses/domination.py +++ b/artiq/compiler/analyses/domination.py @@ -37,3 +37,30 @@ class DominatorTree: if not changed: break + +class PostDominatorTree: + def __init__(self, func): + exits = [block for block in func.basic_blocks if none(block.successors())] + + self.dominated_by = { exit: {exit} for exit in exits } + for block in func.basic_blocks: + if block != entry: + self.dominated_by[block] = set(func.basic_blocks) + + successors = {block: block.successors() for block in func.basic_blocks} + while True: + changed = False + + for block in func.basic_blocks: + if block in exits: + continue + + new_dominated_by = {block}.union( + reduce(lambda a, b: a.intersection(b), + (self.dominated_by[pred] for pred in successors[block]))) + if new_dominated_by != self.dominated_by[block]: + self.dominated_by[block] = new_dominated_by + changed = True + + if not changed: + break From a0c6f75f9bfb9d1c40da0e27cf0afc2559ecbba9 Mon Sep 17 00:00:00 2001 From: whitequark Date: Sat, 7 Nov 2015 15:14:19 +0300 Subject: [PATCH 305/369] compiler.types: fix obsolete iodelay references. --- artiq/compiler/types.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/artiq/compiler/types.py b/artiq/compiler/types.py index 2490b866f..e7570d607 100644 --- a/artiq/compiler/types.py +++ b/artiq/compiler/types.py @@ -262,9 +262,9 @@ class TRPCFunction(TFunction): attributes = OrderedDict() def __init__(self, args, optargs, ret, service): - super().__init__(args, optargs, ret, - delay=FixedDelay(iodelay.Constant(0))) + super().__init__(args, optargs, ret) self.service = service + self.delay = TFixedDelay(iodelay.Const(0)) def unify(self, other): if isinstance(other, TRPCFunction) and \ @@ -285,9 +285,9 @@ class TCFunction(TFunction): attributes = OrderedDict() def __init__(self, args, ret, name): - super().__init__(args, OrderedDict(), ret, - delay=FixedDelay(iodelay.Constant(0))) - self.name = name + super().__init__(args, OrderedDict(), ret) + self.name = name + self.delay = TFixedDelay(iodelay.Const(0)) def unify(self, other): if isinstance(other, TCFunction) and \ From 0b8535dc67af2a05760547f69f0199a1379a67f3 Mon Sep 17 00:00:00 2001 From: whitequark Date: Sat, 7 Nov 2015 15:14:43 +0300 Subject: [PATCH 306/369] Update .gitignore. --- .gitignore | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.gitignore b/.gitignore index 0fa4b8772..8153f9c71 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,8 @@ examples/master/dataset_db.pyon examples/sim/dataset_db.pyon Output/ /lit-test/libartiq_support/libartiq_support.so + +# for developer convenience +/test*.py +/device_db.pyon +/dataset_db.pyon From 73c22b0b1e2e380d618eccec7d0538ea143f45fa Mon Sep 17 00:00:00 2001 From: whitequark Date: Sat, 7 Nov 2015 15:17:31 +0300 Subject: [PATCH 307/369] Fix tests. --- lit-test/libartiq_support/Makefile | 4 ++-- lit-test/test/lit.cfg | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lit-test/libartiq_support/Makefile b/lit-test/libartiq_support/Makefile index bac7a11c9..0406dee63 100644 --- a/lit-test/libartiq_support/Makefile +++ b/lit-test/libartiq_support/Makefile @@ -1,4 +1,4 @@ CC ?= clang -libartiq_support.so: ../../soc/runtime/artiq_personality.c artiq_terminate.c artiq_time.c - $(CC) -std=c99 -Wall -Werror -I. -I../../soc/runtime -g -fPIC -shared -o $@ $^ +libartiq_support.so: ../../artiq/runtime/artiq_personality.c artiq_terminate.c artiq_time.c + $(CC) -std=c99 -Wall -Werror -I. -I../../artiq/runtime -g -fPIC -shared -o $@ $^ diff --git a/lit-test/test/lit.cfg b/lit-test/test/lit.cfg index 35da31b04..08f024e55 100644 --- a/lit-test/test/lit.cfg +++ b/lit-test/test/lit.cfg @@ -8,7 +8,7 @@ config.name = 'ARTIQ' config.test_format = lit.formats.ShTest() config.suffixes = ['.py'] -python_executable = 'python3' +python_executable = 'python3.5' harness = '{} {}'.format(python_executable, os.path.join(root, 'harness.py')) config.substitutions.append( ('%python', harness) ) From 19fae9181cc4bdd3e343ddc55ae9d95db26caa8d Mon Sep 17 00:00:00 2001 From: whitequark Date: Mon, 9 Nov 2015 11:51:54 +0300 Subject: [PATCH 308/369] compiler.analyses.domination: implement new dominator tree algorithm. --- artiq/compiler/analyses/domination.py | 181 ++++++++++++++++------ artiq/compiler/validators/local_access.py | 6 +- artiq/test/compiler/domination.py | 115 ++++++++++++++ 3 files changed, 253 insertions(+), 49 deletions(-) create mode 100644 artiq/test/compiler/domination.py diff --git a/artiq/compiler/analyses/domination.py b/artiq/compiler/analyses/domination.py index 50aacc52d..7b6b54f72 100644 --- a/artiq/compiler/analyses/domination.py +++ b/artiq/compiler/analyses/domination.py @@ -2,65 +2,154 @@ :class:`DominatorTree` computes the dominance relation over control flow graphs. -See http://www.cs.colostate.edu/~mstrout/CS553/slides/lecture04.pdf. +See http://www.cs.rice.edu/~keith/EMBED/dom.pdf. """ -from functools import reduce, cmp_to_key +class GenericDominatorTree: + def __init__(self): + self._assign_names() + self._compute() -# Key Idea -# If a node dominates all -# predecessors of node n, then it -# also dominates node n -class DominatorTree: - def __init__(self, func): - entry = func.entry() + def _start_blocks(self): + """ + Returns a starting collection of basic blocks (entry block + for dominator tree and exit blocks for postdominator tree). + """ + raise NotImplementedError - self.dominated_by = { entry: {entry} } - for block in func.basic_blocks: - if block != entry: - self.dominated_by[block] = set(func.basic_blocks) + def _next_blocks(self, block): + """ + Returns the collection of blocks to be traversed after `block` + (successors for dominator tree and predecessors for postdominator + tree). + """ + raise NotImplementedError + + def _prev_blocks(self, block): + """ + Returns the collection of blocks to be traversed before `block` + (predecessors for dominator tree and successors for postdominator + tree). + """ + raise NotImplementedError + + def _assign_names(self): + """Assigns names to basic blocks in postorder.""" + visited = set() + postorder = [] + + def visit(block): + visited.add(block) + for next_block in self._next_blocks(block): + if next_block not in visited: + visit(next_block) + postorder.append(block) + + for block in self._start_blocks(): + visit(block) + + self._last_name = len(postorder) + self._block_of_name = postorder + self._name_of_block = {} + for block_name, block in enumerate(postorder): + # print("name", block_name + 1, block.name) + self._name_of_block[block] = block_name + + def _start_block_names(self): + for block in self._start_blocks(): + yield self._name_of_block[block] + + def _next_block_names(self, block_name): + for block in self._next_blocks(self._block_of_name[block_name]): + yield self._name_of_block[block] + + def _prev_block_names(self, block_name): + for block in self._prev_blocks(self._block_of_name[block_name]): + yield self._name_of_block[block] + + 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 = {} + + for block_name in range(self._last_name): + self._doms[block_name] = None + + start_block_names = set() + for block_name in self._start_block_names(): + self._doms[block_name] = block_name + start_block_names.add(block_name) + + changed = True + while changed: + # print("doms", {k+1: self._doms[k]+1 if self._doms[k] is not None else None for k in self._doms}) - predecessors = {block: block.predecessors() for block in func.basic_blocks} - while True: changed = False - - for block in func.basic_blocks: - if block == entry: + for block_name in reversed(range(self._last_name)): + if block_name in start_block_names: continue - new_dominated_by = {block}.union( - reduce(lambda a, b: a.intersection(b), - (self.dominated_by[pred] for pred in predecessors[block]))) - if new_dominated_by != self.dominated_by[block]: - self.dominated_by[block] = new_dominated_by + 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) + + # print("block_name", block_name + 1, "new_idom", new_idom + 1) + for prev_block_name in prev_block_names: + # print("prev_block_name", prev_block_name + 1) + if self._doms[prev_block_name] is not None: + new_idom = self._intersect(prev_block_name, new_idom) + # print("new_idom+", new_idom + 1) + + if self._doms[block_name] != new_idom: + self._doms[block_name] = new_idom changed = True - if not changed: - break + def immediate_dominator(self, block): + return self._block_of_name[self._doms[self._name_of_block[block]]] -class PostDominatorTree: - def __init__(self, func): - exits = [block for block in func.basic_blocks if none(block.successors())] + def dominators(self, block): + yield block - self.dominated_by = { exit: {exit} for exit in exits } - for block in func.basic_blocks: - if block != entry: - self.dominated_by[block] = set(func.basic_blocks) + block_name = self._name_of_block[block] + while block_name != self._doms[block_name]: + block_name = self._doms[block_name] + yield self._block_of_name[block_name] - successors = {block: block.successors() for block in func.basic_blocks} - while True: - changed = False +class DominatorTree(GenericDominatorTree): + def __init__(self, function): + self.function = function + super().__init__() - for block in func.basic_blocks: - if block in exits: - continue + def _start_blocks(self): + return [self.function.entry()] - new_dominated_by = {block}.union( - reduce(lambda a, b: a.intersection(b), - (self.dominated_by[pred] for pred in successors[block]))) - if new_dominated_by != self.dominated_by[block]: - self.dominated_by[block] = new_dominated_by - changed = True + def _next_blocks(self, block): + return block.successors() - if not changed: - break + def _prev_blocks(self, block): + return block.predecessors() + +class PostDominatorTree(GenericDominatorTree): + def __init__(self, function): + self.function = function + super().__init__() + + def _start_blocks(self): + return [block for block in self.function.basic_blocks + if none(block.successors())] + + def _next_blocks(self, block): + return block.predecessors() + + def _prev_blocks(self, block): + return block.successors() diff --git a/artiq/compiler/validators/local_access.py b/artiq/compiler/validators/local_access.py index f3429140e..fed87092b 100644 --- a/artiq/compiler/validators/local_access.py +++ b/artiq/compiler/validators/local_access.py @@ -38,8 +38,8 @@ class LocalAccessValidator: # Traverse the acyclic graph made of basic blocks and forward edges only, # while updating the environment state. - dom = analyses.DominatorTree(func) - state = {} + domtree = analyses.DominatorTree(func) + state = {} def traverse(block): # Have we computed the state of this block already? if block in state: @@ -48,7 +48,7 @@ class LocalAccessValidator: # 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 dom.dominated_by[pred]] + if block not in domtree.dominators(pred)] # Figure out what the state is before the leader # instruction of this block. diff --git a/artiq/test/compiler/domination.py b/artiq/test/compiler/domination.py new file mode 100644 index 000000000..fa1c79ea0 --- /dev/null +++ b/artiq/test/compiler/domination.py @@ -0,0 +1,115 @@ +import unittest +from artiq.compiler.analyses.domination import DominatorTree, PostDominatorTree + +class MockBasicBlock: + def __init__(self, name): + self.name = name + self._successors = [] + self._predecessors = [] + + def successors(self): + return self._successors + + def predecessors(self): + return self._predecessors + + def set_successors(self, successors): + self._successors = list(successors) + for block in self._successors: + block._predecessors.append(self) + +class MockFunction: + def __init__(self, entry, basic_blocks): + self._entry = entry + self.basic_blocks = basic_blocks + + def entry(self): + return self._entry + +def makefn(entry_name, graph): + blocks = {} + for block_name in graph: + blocks[block_name] = MockBasicBlock(block_name) + for block_name in graph: + successors = list(map(lambda name: blocks[name], graph[block_name])) + blocks[block_name].set_successors(successors) + return MockFunction(blocks[entry_name], blocks.values()) + +def dom(function, domtree): + dom = {} + for block in function.basic_blocks: + dom[block.name] = [dom_block.name for dom_block in domtree.dominators(block)] + return dom + +def idom(function, domtree): + idom = {} + for block in function.basic_blocks: + idom[block.name] = domtree.immediate_dominator(block).name + return idom + +class TestDominatorTree(unittest.TestCase): + def test_linear(self): + func = makefn('A', { + 'A': ['B'], + 'B': ['C'], + 'C': [] + }) + domtree = DominatorTree(func) + self.assertEqual({ + 'C': 'B', 'B': 'A', 'A': 'A' + }, idom(func, domtree)) + self.assertEqual({ + 'C': ['C', 'B', 'A'], 'B': ['B', 'A'], 'A': ['A'] + }, dom(func, domtree)) + + def test_diamond(self): + func = makefn('A', { + 'A': ['C', 'B'], + 'B': ['D'], + 'C': ['D'], + 'D': [] + }) + domtree = DominatorTree(func) + self.assertEqual({ + 'D': 'A', 'C': 'A', 'B': 'A', 'A': 'A' + }, idom(func, domtree)) + + def test_combined(self): + func = makefn('A', { + 'A': ['B', 'D'], + 'B': ['C'], + 'C': ['E'], + 'D': ['E'], + 'E': [] + }) + domtree = DominatorTree(func) + self.assertEqual({ + 'A': 'A', 'B': 'A', 'C': 'B', 'D': 'A', 'E': 'A' + }, idom(func, domtree)) + + def test_figure_2(self): + func = makefn(5, { + 5: [3, 4], + 4: [1], + 3: [2], + 2: [1], + 1: [2] + }) + domtree = DominatorTree(func) + self.assertEqual({ + 1: 5, 2: 5, 3: 5, 4: 5, 5: 5 + }, idom(func, domtree)) + + def test_figure_4(self): + func = makefn(6, { + 6: [4, 5], + 5: [1], + 4: [3, 2], + 3: [2], + 2: [1, 3], + 1: [2] + }) + domtree = DominatorTree(func) + self.assertEqual({ + 1: 6, 2: 6, 3: 6, 4: 6, 5: 6, 6: 6 + }, idom(func, domtree)) From 9670939ca6ca65afb19513f22033f65e043bd5d2 Mon Sep 17 00:00:00 2001 From: whitequark Date: Mon, 9 Nov 2015 12:49:27 +0300 Subject: [PATCH 309/369] compiler.analyses.domination: fix PostDominatorTree. --- artiq/compiler/analyses/domination.py | 128 +++++++++++--------------- artiq/test/compiler/domination.py | 53 ++++++++++- 2 files changed, 107 insertions(+), 74 deletions(-) diff --git a/artiq/compiler/analyses/domination.py b/artiq/compiler/analyses/domination.py index 7b6b54f72..53aa3a2f1 100644 --- a/artiq/compiler/analyses/domination.py +++ b/artiq/compiler/analyses/domination.py @@ -10,63 +10,21 @@ class GenericDominatorTree: self._assign_names() self._compute() - def _start_blocks(self): - """ - Returns a starting collection of basic blocks (entry block - for dominator tree and exit blocks for postdominator tree). - """ + def _traverse_in_postorder(self): raise NotImplementedError - def _next_blocks(self, block): - """ - Returns the collection of blocks to be traversed after `block` - (successors for dominator tree and predecessors for postdominator - tree). - """ - raise NotImplementedError - - def _prev_blocks(self, block): - """ - Returns the collection of blocks to be traversed before `block` - (predecessors for dominator tree and successors for postdominator - tree). - """ + def _prev_block_names(self, block): raise NotImplementedError def _assign_names(self): - """Assigns names to basic blocks in postorder.""" - visited = set() - postorder = [] + postorder = self._traverse_in_postorder() - def visit(block): - visited.add(block) - for next_block in self._next_blocks(block): - if next_block not in visited: - visit(next_block) - postorder.append(block) - - for block in self._start_blocks(): - visit(block) - - self._last_name = len(postorder) + self._start_name = len(postorder) - 1 self._block_of_name = postorder self._name_of_block = {} for block_name, block in enumerate(postorder): - # print("name", block_name + 1, block.name) self._name_of_block[block] = block_name - def _start_block_names(self): - for block in self._start_blocks(): - yield self._name_of_block[block] - - def _next_block_names(self, block_name): - for block in self._next_blocks(self._block_of_name[block_name]): - yield self._name_of_block[block] - - def _prev_block_names(self, block_name): - for block in self._prev_blocks(self._block_of_name[block_name]): - yield self._name_of_block[block] - def _intersect(self, block_name_1, block_name_2): finger_1, finger_2 = block_name_1, block_name_2 while finger_1 != finger_2: @@ -79,23 +37,23 @@ class GenericDominatorTree: def _compute(self): self._doms = {} - for block_name in range(self._last_name): - self._doms[block_name] = None + # Start block dominates itself. + self._doms[self._start_name] = self._start_name - start_block_names = set() - for block_name in self._start_block_names(): - self._doms[block_name] = block_name - start_block_names.add(block_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: - # print("doms", {k+1: self._doms[k]+1 if self._doms[k] is not None else None for k in self._doms}) - changed = False - for block_name in reversed(range(self._last_name)): - if block_name in start_block_names: - continue + # 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: @@ -103,12 +61,10 @@ class GenericDominatorTree: else: prev_block_names.append(prev_block_name) - # print("block_name", block_name + 1, "new_idom", new_idom + 1) + # Find a common previous block for prev_block_name in prev_block_names: - # print("prev_block_name", prev_block_name + 1) if self._doms[prev_block_name] is not None: new_idom = self._intersect(prev_block_name, new_idom) - # print("new_idom+", new_idom + 1) if self._doms[block_name] != new_idom: self._doms[block_name] = new_idom @@ -130,26 +86,52 @@ class DominatorTree(GenericDominatorTree): self.function = function super().__init__() - def _start_blocks(self): - return [self.function.entry()] + def _traverse_in_postorder(self): + postorder = [] - def _next_blocks(self, block): - return block.successors() + 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) - def _prev_blocks(self, block): - return block.predecessors() + visit(self.function.entry()) + + return postorder + + def _prev_block_names(self, block_name): + for block in self._block_of_name[block_name].predecessors(): + yield self._name_of_block[block] class PostDominatorTree(GenericDominatorTree): def __init__(self, function): self.function = function super().__init__() - def _start_blocks(self): - return [block for block in self.function.basic_blocks - if none(block.successors())] + def _traverse_in_postorder(self): + postorder = [] - def _next_blocks(self, block): - return block.predecessors() + 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) - def _prev_blocks(self, block): - return block.successors() + 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/test/compiler/domination.py b/artiq/test/compiler/domination.py index fa1c79ea0..0c4334617 100644 --- a/artiq/test/compiler/domination.py +++ b/artiq/test/compiler/domination.py @@ -44,7 +44,8 @@ def dom(function, domtree): def idom(function, domtree): idom = {} for block in function.basic_blocks: - idom[block.name] = domtree.immediate_dominator(block).name + idom_block = domtree.immediate_dominator(block) + idom[block.name] = idom_block.name if idom_block else None return idom class TestDominatorTree(unittest.TestCase): @@ -113,3 +114,53 @@ class TestDominatorTree(unittest.TestCase): self.assertEqual({ 1: 6, 2: 6, 3: 6, 4: 6, 5: 6, 6: 6 }, idom(func, domtree)) + +class TestPostDominatorTree(unittest.TestCase): + def test_linear(self): + func = makefn('A', { + 'A': ['B'], + 'B': ['C'], + 'C': [] + }) + domtree = PostDominatorTree(func) + self.assertEqual({ + 'A': 'B', 'B': 'C', 'C': None + }, idom(func, domtree)) + + def test_diamond(self): + func = makefn('A', { + 'A': ['B', 'D'], + 'B': ['C'], + 'C': ['E'], + 'D': ['E'], + 'E': [] + }) + domtree = PostDominatorTree(func) + self.assertEqual({ + 'E': None, 'D': 'E', 'C': 'E', 'B': 'C', 'A': 'E' + }, idom(func, domtree)) + + def test_multi_exit(self): + func = makefn('A', { + 'A': ['B', 'C'], + 'B': [], + 'C': [] + }) + domtree = PostDominatorTree(func) + self.assertEqual({ + 'A': None, 'B': None, 'C': None + }, idom(func, domtree)) + + def test_multi_exit_diamond(self): + func = makefn('A', { + 'A': ['B', 'C'], + 'B': ['D'], + 'C': ['D'], + 'D': ['E', 'F'], + 'E': [], + 'F': [] + }) + domtree = PostDominatorTree(func) + self.assertEqual({ + 'A': 'D', 'B': 'D', 'C': 'D', 'D': None, 'E': None, 'F': None + }, idom(func, domtree)) From c8cfa7c7bdec8b51effda22e0a78f0de13223528 Mon Sep 17 00:00:00 2001 From: whitequark Date: Sun, 15 Nov 2015 23:09:40 +0300 Subject: [PATCH 310/369] compiler: give suggestions in diagnostics for unbound variable. This uses the Jaro-Winkler edit distance, which seemed like the best fit for identifiers, even though it is intended for people's names. --- artiq/compiler/embedding.py | 29 +++++++++++++++++++++++++---- setup.py | 3 ++- 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/artiq/compiler/embedding.py b/artiq/compiler/embedding.py index a2cf1aa34..2a3fba5f3 100644 --- a/artiq/compiler/embedding.py +++ b/artiq/compiler/embedding.py @@ -11,6 +11,8 @@ 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 jaro_winkler + from ..language import core as language_core from . import types, builtins, asttyped, prelude from .transforms import ASTTypedRewriter, Inferencer, IntMonomorphizer @@ -225,10 +227,29 @@ class StitchingASTTypedRewriter(ASTTypedRewriter): if node.id in self.host_environment: return self.quote(self.host_environment[node.id], node.loc) else: - diag = diagnostic.Diagnostic("fatal", - "name '{name}' is not bound to anything", {"name":node.id}, - node.loc) - self.engine.process(diag) + suggestion = self._most_similar_ident(node.id) + 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) + + def _most_similar_ident(self, id): + names = set() + names.update(self.host_environment.keys()) + for typing_env in reversed(self.env_stack): + names.update(typing_env.keys()) + + 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: + return sorted_names[0] class StitchingInferencer(Inferencer): def __init__(self, engine, value_map, quote): diff --git a/setup.py b/setup.py index c76831ad2..d5dc00973 100755 --- a/setup.py +++ b/setup.py @@ -27,7 +27,8 @@ requirements = [ "sphinx", "sphinx-argparse", "pyserial", "numpy", "scipy", "python-dateutil", "prettytable", "h5py", "pydaqmx", "pyelftools", "quamash", "pyqtgraph", "pygit2", "aiohttp", - "llvmlite_artiq", "pythonparser", "lit", "OutputCheck", + "llvmlite_artiq", "pythonparser", "python-Levenshtein", + "lit", "OutputCheck", ] scripts = [ From eefa9e2ea6bccad66ab580f051f05c2bef1b7f18 Mon Sep 17 00:00:00 2001 From: whitequark Date: Tue, 17 Nov 2015 00:02:15 +0300 Subject: [PATCH 311/369] transforms.inferencer: fix typo. --- artiq/compiler/transforms/inferencer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/artiq/compiler/transforms/inferencer.py b/artiq/compiler/transforms/inferencer.py index 8ca560285..f823014f8 100644 --- a/artiq/compiler/transforms/inferencer.py +++ b/artiq/compiler/transforms/inferencer.py @@ -935,7 +935,7 @@ class Inferencer(algorithm.Visitor): self.generic_visit(node) typ = node.context_expr.type - if not types.is_builtin(typ, "parallel") or types.is_builtin(typ, "sequential"): + if not (types.is_builtin(typ, "parallel") or types.is_builtin(typ, "sequential")): diag = diagnostic.Diagnostic("error", "value of type {type} cannot act as a context manager", {"type": types.TypePrinter().name(typ)}, From fd46690cf51fde7479131d59aaac68ee73928968 Mon Sep 17 00:00:00 2001 From: whitequark Date: Tue, 17 Nov 2015 00:23:34 +0300 Subject: [PATCH 312/369] compiler: make IR dumps vastly more readable. --- artiq/compiler/ir.py | 94 ++++++++++++++++++++++----------------- artiq/compiler/targets.py | 4 +- 2 files changed, 55 insertions(+), 43 deletions(-) diff --git a/artiq/compiler/ir.py b/artiq/compiler/ir.py index bf487960d..aa428f9b9 100644 --- a/artiq/compiler/ir.py +++ b/artiq/compiler/ir.py @@ -51,6 +51,9 @@ class 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. @@ -62,11 +65,11 @@ class Constant(Value): super().__init__(typ) self.value = value - def as_operand(self): - return str(self) + def as_operand(self, type_printer): + return self.as_entity(type_printer) - def __str__(self): - return "{} {}".format(types.TypePrinter().name(self.type), + def as_entity(self, type_printer): + return "{} {}".format(type_printer.name(self.type), repr(self.value)) class NamedValue(Value): @@ -99,8 +102,8 @@ class NamedValue(Value): def _detach(self): self.function = None - def as_operand(self): - return "{} %{}".format(types.TypePrinter().name(self.type), + def as_operand(self, type_printer): + return "{} %{}".format(type_printer.name(self.type), escape_name(self.name)) class User(NamedValue): @@ -183,18 +186,19 @@ class Instruction(User): else: self.erase() - def _operands_as_string(self): - return ", ".join([operand.as_operand() for operand in self.operands]) + def _operands_as_string(self, type_printer): + return ", ".join([operand.as_operand(type_printer) for operand in self.operands]) - def __str__(self): + def as_entity(self, type_printer): if builtins.is_none(self.type): prefix = "" else: prefix = "%{} = {} ".format(escape_name(self.name), - types.TypePrinter().name(self.type)) + type_printer.name(self.type)) if any(self.operands): - return "{}{} {}".format(prefix, self.opcode(), self._operands_as_string()) + return "{}{} {}".format(prefix, self.opcode(), + self._operands_as_string(type_printer)) else: return "{}{}".format(prefix, self.opcode()) @@ -248,15 +252,16 @@ class Phi(Instruction): self.operands[index].uses.remove(self) del self.operands[index - 1:index + 1] - def __str__(self): + def as_entity(self, type_printer): if builtins.is_none(self.type): prefix = "" else: prefix = "%{} = {} ".format(escape_name(self.name), - types.TypePrinter().name(self.type)) + type_printer.name(self.type)) if any(self.operands): - operand_list = ["%{} => {}".format(escape_name(block.name), value.as_operand()) + 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: @@ -346,7 +351,7 @@ class BasicBlock(NamedValue): def predecessors(self): return [use.basic_block for use in self.uses if isinstance(use, Terminator)] - def __str__(self): + def as_entity(self, type_printer): # Header lines = ["{}:".format(escape_name(self.name))] if self.function is not None: @@ -372,7 +377,7 @@ class BasicBlock(NamedValue): line_desc = "{}:{}".format(loc.source_buffer.name, loc.line()) lines += ["; {} {}".format(line_desc, line.rstrip("\n")) for line in source_lines] - lines.append(" " + str(insn)) + lines.append(" " + insn.as_entity(type_printer)) return "\n".join(lines) @@ -384,8 +389,8 @@ class Argument(NamedValue): A function argument. """ - def __str__(self): - return self.as_operand() + def as_entity(self, type_printer): + return self.as_operand(type_printer) class Function: """ @@ -447,18 +452,20 @@ class Function: for basic_block in self.basic_blocks: yield from iter(basic_block.instructions) - def __str__(self): - printer = types.TypePrinter() + def as_entity(self, type_printer): lines = [] lines.append("{} {}({}) {{ ; type: {}".format( - printer.name(self.type.ret), self.name, - ", ".join([arg.as_operand() for arg in self.arguments]), - printer.name(self.type))) + type_printer.name(self.type.ret), self.name, + ", ".join([arg.as_operand(type_printer) for arg in self.arguments]), + type_printer.name(self.type))) for block in self.basic_blocks: - lines.append(str(block)) + 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): @@ -511,7 +518,7 @@ class EnvironmentArgument(Argument): A function argument specifying an outer environment. """ - def as_operand(self): + def as_operand(self, type_printer): return "environment(...) %{}".format(escape_name(self.name)) class Alloc(Instruction): @@ -527,12 +534,12 @@ class Alloc(Instruction): def opcode(self): return "alloc" - def as_operand(self): + 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() + return super().as_operand(type_printer) class GetLocal(Instruction): """ @@ -875,8 +882,8 @@ class Call(Instruction): def arguments(self): return self.operands[1:] - def __str__(self): - result = super().__str__() + 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 @@ -1003,9 +1010,10 @@ class IndirectBranch(Terminator): destination.uses.add(self) self.operands.append(destination) - def _operands_as_string(self): - return "{}, [{}]".format(self.operands[0].as_operand(), - ", ".join([dest.as_operand() for dest in self.operands[1:]])) + 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): """ @@ -1125,14 +1133,14 @@ class Invoke(Terminator): def exception_target(self): return self.operands[-1] - def _operands_as_string(self): - result = ", ".join([operand.as_operand() for operand in self.operands[:-2]]) - result += " to {} unwind {}".format(self.operands[-2].as_operand(), - self.operands[-1].as_operand()) + 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 __str__(self): - result = super().__str__() + 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 @@ -1169,14 +1177,16 @@ class LandingPad(Terminator): self.types.append(typ.find() if typ is not None else None) target.uses.add(self) - def _operands_as_string(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())) + table.append("... => {}".format(target.as_operand(type_printer))) else: - table.append("{} => {}".format(types.TypePrinter().name(typ), target.as_operand())) - return "cleanup {}, [{}]".format(self.cleanup().as_operand(), ", ".join(table)) + table.append("{} => {}".format(type_printer.name(typ), + target.as_operand(type_printer))) + return "cleanup {}, [{}]".format(self.cleanup().as_operand(type_printer), + ", ".join(table)) class Parallel(Terminator): """ diff --git a/artiq/compiler/targets.py b/artiq/compiler/targets.py index 2194b73b5..8889ee67c 100644 --- a/artiq/compiler/targets.py +++ b/artiq/compiler/targets.py @@ -1,4 +1,5 @@ import os, sys, tempfile, subprocess +from artiq.compiler import types from llvmlite_artiq import ir as ll, binding as llvm llvm.initialize() @@ -75,8 +76,9 @@ class Target: if os.getenv("ARTIQ_DUMP_IR"): print("====== ARTIQ IR DUMP ======", file=sys.stderr) + type_printer = types.TypePrinter() for function in module.artiq_ir: - print(function, file=sys.stderr) + print(function.as_entity(type_printer), file=sys.stderr) llmod = module.build_llvm_ir(self) llparsedmod = llvm.parse_assembly(str(llmod)) From 629aacec090ae7d8c9f245c27aa39d254453d809 Mon Sep 17 00:00:00 2001 From: whitequark Date: Tue, 17 Nov 2015 00:51:22 +0300 Subject: [PATCH 313/369] compiler.iodelay: add forgotten Conv.__eq__. --- artiq/compiler/iodelay.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/artiq/compiler/iodelay.py b/artiq/compiler/iodelay.py index 165ce3950..71e137df9 100644 --- a/artiq/compiler/iodelay.py +++ b/artiq/compiler/iodelay.py @@ -85,6 +85,11 @@ class Conv(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() From e67705dc270823ee57beec83b809cc18359bf926 Mon Sep 17 00:00:00 2001 From: whitequark Date: Tue, 17 Nov 2015 00:51:37 +0300 Subject: [PATCH 314/369] compiler.iodelay: fix typo in BinOp.__eq__. --- artiq/compiler/iodelay.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/artiq/compiler/iodelay.py b/artiq/compiler/iodelay.py index 71e137df9..b575caac7 100644 --- a/artiq/compiler/iodelay.py +++ b/artiq/compiler/iodelay.py @@ -121,7 +121,7 @@ class BinOp(Expr): return "{} {} {}".format(lhs, self._symbol, rhs) def __eq__(lhs, rhs): - return rhs.__class__ == lhs.__class__ and lhs.lhs == rhs.lhs and rhs.lhs == rhs.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)) From a2d73c8b05c4b229ebcc69e18b0bdacb556b807f Mon Sep 17 00:00:00 2001 From: whitequark Date: Tue, 17 Nov 2015 00:51:56 +0300 Subject: [PATCH 315/369] compiler.types: dump type variable iodelay explicitly. --- artiq/compiler/types.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/artiq/compiler/types.py b/artiq/compiler/types.py index e7570d607..9cac1594a 100644 --- a/artiq/compiler/types.py +++ b/artiq/compiler/types.py @@ -623,8 +623,9 @@ class TypePrinter(object): signature = "(%s)->%s" % (", ".join(args), self.name(typ.ret)) delay = typ.delay.find() - if not (isinstance(delay, TVar) or - delay.is_fixed() and iodelay.is_zero(delay.duration)): + if isinstance(delay, TVar): + signature += " delay({})".format(self.name(delay)) + elif not (delay.is_fixed() and iodelay.is_zero(delay.duration)): signature += " " + self.name(delay) if isinstance(typ, TRPCFunction): From 841e01a8bb042502e7c0aa81dabfa021dd580805 Mon Sep 17 00:00:00 2001 From: whitequark Date: Tue, 17 Nov 2015 00:53:12 +0300 Subject: [PATCH 316/369] compiler.iodelay: fix typo in Var.__eq__. --- artiq/compiler/iodelay.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/artiq/compiler/iodelay.py b/artiq/compiler/iodelay.py index b575caac7..44b447325 100644 --- a/artiq/compiler/iodelay.py +++ b/artiq/compiler/iodelay.py @@ -66,7 +66,7 @@ class Var(Expr): return self.name def __eq__(lhs, rhs): - return rhs.__class__ == lhs.__class__ and lhs.value == rhs.value + return rhs.__class__ == lhs.__class__ and lhs.name == rhs.name def free_vars(self): return {self.name} From 10f82ff2c8c779b5f802f573c2a8b74f36064ae1 Mon Sep 17 00:00:00 2001 From: whitequark Date: Tue, 17 Nov 2015 00:59:09 +0300 Subject: [PATCH 317/369] transforms.iodelay_estimator: do not unify indeterminate delays. --- artiq/compiler/transforms/iodelay_estimator.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/artiq/compiler/transforms/iodelay_estimator.py b/artiq/compiler/transforms/iodelay_estimator.py index 733c93e41..9487fde0a 100644 --- a/artiq/compiler/transforms/iodelay_estimator.py +++ b/artiq/compiler/transforms/iodelay_estimator.py @@ -101,6 +101,12 @@ class IODelayEstimator(algorithm.Visitor): 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: typ.delay.unify(delay) except types.UnificationError as e: From 44d0a354099d7a9e5aa07af772120611d0012982 Mon Sep 17 00:00:00 2001 From: whitequark Date: Tue, 17 Nov 2015 00:59:40 +0300 Subject: [PATCH 318/369] transforms.iodelay_estimator: actually iterate to fixpoint. --- artiq/compiler/transforms/iodelay_estimator.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/artiq/compiler/transforms/iodelay_estimator.py b/artiq/compiler/transforms/iodelay_estimator.py index 9487fde0a..f8c190fb4 100644 --- a/artiq/compiler/transforms/iodelay_estimator.py +++ b/artiq/compiler/transforms/iodelay_estimator.py @@ -108,7 +108,10 @@ class IODelayEstimator(algorithm.Visitor): 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", From 9e0a5b940494a5708b69bc8a4cd95803be03ad70 Mon Sep 17 00:00:00 2001 From: whitequark Date: Tue, 17 Nov 2015 01:00:10 +0300 Subject: [PATCH 319/369] transforms.iodelay_estimator: skip statements, not modules on _UnknownDelay. --- artiq/compiler/transforms/iodelay_estimator.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/artiq/compiler/transforms/iodelay_estimator.py b/artiq/compiler/transforms/iodelay_estimator.py index f8c190fb4..aaae55676 100644 --- a/artiq/compiler/transforms/iodelay_estimator.py +++ b/artiq/compiler/transforms/iodelay_estimator.py @@ -80,8 +80,12 @@ class IODelayEstimator(algorithm.Visitor): def visit_ModuleT(self, node): try: - self.visit(node.body) - except (diagnostic.Error, _UnknownDelay): + for stmt in node.body: + try: + self.visit(stmt) + except _UnknownDelay: + pass # more luck next time? + except diagnostic.Error: pass # we don't care; module-level code is never interleaved def visit_function(self, args, body, typ, loc): From 3fb12b74d640039e42cce354444d2b3c26c81c1c Mon Sep 17 00:00:00 2001 From: whitequark Date: Tue, 17 Nov 2015 01:04:08 +0300 Subject: [PATCH 320/369] lit-test: update to follow IR serialization changes. --- lit-test/test/inferencer/class.py | 6 +++--- lit-test/test/inferencer/error_method.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lit-test/test/inferencer/class.py b/lit-test/test/inferencer/class.py index db8ddd27c..1249474a0 100644 --- a/lit-test/test/inferencer/class.py +++ b/lit-test/test/inferencer/class.py @@ -8,12 +8,12 @@ class c: def m(self): pass -# CHECK-L: c:NoneType delay('b), m: (self:)->NoneType delay('c)}> c # CHECK-L: .a:int(width='a) c.a -# CHECK-L: .f:()->NoneType +# CHECK-L: .f:()->NoneType delay('b) c.f -# CHECK-L: .m:method(fn=(self:)->NoneType, self=) +# CHECK-L: .m:method(fn=(self:)->NoneType delay('c), self=) c().m() diff --git a/lit-test/test/inferencer/error_method.py b/lit-test/test/inferencer/error_method.py index bf9f87b86..cb4f77075 100644 --- a/lit-test/test/inferencer/error_method.py +++ b/lit-test/test/inferencer/error_method.py @@ -8,7 +8,7 @@ class c: def g(self): pass -# CHECK-L: ${LINE:+1}: error: function 'f()->NoneType' of class 'c' cannot accept a self argument +# CHECK-L: ${LINE:+1}: error: function 'f()->NoneType delay('a)' of class 'c' cannot accept a self argument c().f() c.g(1) From 80f0bfe0ad8bbc56d5dbc405daad1c2609873b5d Mon Sep 17 00:00:00 2001 From: whitequark Date: Tue, 17 Nov 2015 01:04:20 +0300 Subject: [PATCH 321/369] lit-test: add test for iodelay order invariance. --- lit-test/test/iodelay/order_invariance.py | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 lit-test/test/iodelay/order_invariance.py diff --git a/lit-test/test/iodelay/order_invariance.py b/lit-test/test/iodelay/order_invariance.py new file mode 100644 index 000000000..af5adf13c --- /dev/null +++ b/lit-test/test/iodelay/order_invariance.py @@ -0,0 +1,10 @@ +# RUN: %python -m artiq.compiler.testbench.signature %s >%t +# RUN: OutputCheck %s --file-to-check=%t + +# CHECK-L: g: ()->NoneType delay(s->mu(2.0) mu) +def g(): + f() + f() + +def f(): + delay(1.0) From b0c6b70971d35c88b6c7f8b3e04488124ef4eab1 Mon Sep 17 00:00:00 2001 From: whitequark Date: Tue, 17 Nov 2015 01:17:25 +0300 Subject: [PATCH 322/369] transforms.asttyped_rewriter: fix class redefinition diagnostic. --- artiq/compiler/transforms/asttyped_rewriter.py | 2 +- lit-test/test/inferencer/error_class.py | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/artiq/compiler/transforms/asttyped_rewriter.py b/artiq/compiler/transforms/asttyped_rewriter.py index 20aecdb62..084b8a7ca 100644 --- a/artiq/compiler/transforms/asttyped_rewriter.py +++ b/artiq/compiler/transforms/asttyped_rewriter.py @@ -263,7 +263,7 @@ class ASTTypedRewriter(algorithm.Transformer): if node.name in self.env_stack[-1]: diag = diagnostic.Diagnostic("fatal", - "variable '{name}' is already defined", {"name":name}, loc) + "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) diff --git a/lit-test/test/inferencer/error_class.py b/lit-test/test/inferencer/error_class.py index 0cc075b05..dbb399494 100644 --- a/lit-test/test/inferencer/error_class.py +++ b/lit-test/test/inferencer/error_class.py @@ -8,3 +8,9 @@ class a(1): class b: # CHECK-L: ${LINE:+1}: fatal: class body must contain only assignments and function definitions x += 1 + +class c: + pass +# CHECK-L: ${LINE:+1}: fatal: variable 'c' is already defined +class c: + pass From 506725f78abec6ba20ce11397a7441c66158e614 Mon Sep 17 00:00:00 2001 From: whitequark Date: Tue, 17 Nov 2015 01:19:22 +0300 Subject: [PATCH 323/369] transforms.iodelay_estimator: fix handling of methods. --- artiq/compiler/transforms/iodelay_estimator.py | 2 +- lit-test/test/iodelay/class.py | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 lit-test/test/iodelay/class.py diff --git a/artiq/compiler/transforms/iodelay_estimator.py b/artiq/compiler/transforms/iodelay_estimator.py index aaae55676..a28e9e793 100644 --- a/artiq/compiler/transforms/iodelay_estimator.py +++ b/artiq/compiler/transforms/iodelay_estimator.py @@ -258,7 +258,7 @@ class IODelayEstimator(algorithm.Visitor): args = {} for kw_node in node.keywords: args[kw_node.arg] = kw_node.value - for arg_name, arg_node in zip(typ.args, node.args[offset:]): + for arg_name, arg_node in zip(list(typ.args)[offset:], node.args): args[arg_name] = arg_node free_vars = delay.duration.free_vars() diff --git a/lit-test/test/iodelay/class.py b/lit-test/test/iodelay/class.py new file mode 100644 index 000000000..32580c4c6 --- /dev/null +++ b/lit-test/test/iodelay/class.py @@ -0,0 +1,12 @@ +# RUN: %python -m artiq.compiler.testbench.signature %s >%t +# RUN: OutputCheck %s --file-to-check=%t + +# CHECK-L: g: ()->NoneType delay(s->mu(1.0) mu) +def g(i): + i.f(1.0) + +class c: + def f(self, x): + delay(x) + +g(c()) From e619154c81b916f0804694218fdb6c5eb81066bc Mon Sep 17 00:00:00 2001 From: whitequark Date: Tue, 17 Nov 2015 01:22:48 +0300 Subject: [PATCH 324/369] transforms.iodelay_estimator: fix handling of `with sequential`. --- artiq/compiler/transforms/iodelay_estimator.py | 2 +- lit-test/test/iodelay/sequential.py | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 lit-test/test/iodelay/sequential.py diff --git a/artiq/compiler/transforms/iodelay_estimator.py b/artiq/compiler/transforms/iodelay_estimator.py index a28e9e793..cc3da45a1 100644 --- a/artiq/compiler/transforms/iodelay_estimator.py +++ b/artiq/compiler/transforms/iodelay_estimator.py @@ -216,7 +216,7 @@ class IODelayEstimator(algorithm.Visitor): self.current_delay = old_delay self.current_delay += iodelay.Max(delays) - elif len(node.items) == 1 and types.is_builtin(context_expr.type, "serial"): + 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) diff --git a/lit-test/test/iodelay/sequential.py b/lit-test/test/iodelay/sequential.py new file mode 100644 index 000000000..54a776b68 --- /dev/null +++ b/lit-test/test/iodelay/sequential.py @@ -0,0 +1,8 @@ +# RUN: %python -m artiq.compiler.testbench.signature %s >%t +# RUN: OutputCheck %s --file-to-check=%t + +# CHECK-L: f: (a:int(width=64), b:int(width=64))->NoneType delay(a + b mu) +def f(a, b): + with sequential: + delay_mu(a) + delay_mu(b) From 7d2fca291d0344580a8e1f9d0331fb7a47139387 Mon Sep 17 00:00:00 2001 From: whitequark Date: Tue, 17 Nov 2015 01:30:19 +0300 Subject: [PATCH 325/369] compiler.iodelay: add missing import. --- artiq/compiler/iodelay.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/artiq/compiler/iodelay.py b/artiq/compiler/iodelay.py index 44b447325..3941ea22e 100644 --- a/artiq/compiler/iodelay.py +++ b/artiq/compiler/iodelay.py @@ -4,6 +4,8 @@ the statically inferred RTIO delay arising from executing a function. """ +from functools import reduce + class Expr: def __add__(lhs, rhs): assert isinstance(rhs, Expr) From 956d2afcb2da7cb7f2de0bdc9d341f14edf95e63 Mon Sep 17 00:00:00 2001 From: whitequark Date: Tue, 17 Nov 2015 03:06:07 +0300 Subject: [PATCH 326/369] compiler.iodelay: fold Max further. --- artiq/compiler/iodelay.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/artiq/compiler/iodelay.py b/artiq/compiler/iodelay.py index 3941ea22e..ec9a73729 100644 --- a/artiq/compiler/iodelay.py +++ b/artiq/compiler/iodelay.py @@ -213,7 +213,7 @@ class Max(Expr): operand = operand.fold(vars) if isinstance(operand, Const): consts.append(operand.value) - else: + elif operand not in exprs: exprs.append(operand) if any(consts): exprs.append(Const(max(consts))) From de9d7eb2e4f8ae24622721054bf1db00a4815efe Mon Sep 17 00:00:00 2001 From: whitequark Date: Tue, 17 Nov 2015 05:16:43 +0300 Subject: [PATCH 327/369] compiler: add `delay` IR instruction. --- artiq/compiler/asttyped.py | 4 +- artiq/compiler/embedding.py | 2 +- artiq/compiler/ir.py | 37 +++++++++++++++++++ .../compiler/transforms/artiq_ir_generator.py | 29 +++++++++++++-- .../compiler/transforms/asttyped_rewriter.py | 2 +- .../compiler/transforms/iodelay_estimator.py | 9 +++-- 6 files changed, 73 insertions(+), 10 deletions(-) diff --git a/artiq/compiler/asttyped.py b/artiq/compiler/asttyped.py index c9e543ae2..6d7908d8d 100644 --- a/artiq/compiler/asttyped.py +++ b/artiq/compiler/asttyped.py @@ -46,7 +46,9 @@ class BinOpT(ast.BinOp, commontyped): class BoolOpT(ast.BoolOp, commontyped): pass class CallT(ast.Call, commontyped): - pass + """ + :ivar iodelay: (:class:`iodelay.Expr`) + """ class CompareT(ast.Compare, commontyped): pass class DictT(ast.Dict, commontyped): diff --git a/artiq/compiler/embedding.py b/artiq/compiler/embedding.py index 2a3fba5f3..637153dc0 100644 --- a/artiq/compiler/embedding.py +++ b/artiq/compiler/embedding.py @@ -173,7 +173,7 @@ class ASTSynthesizer: for kw, value, (arg_loc, equals_loc) in zip(kwargs, kwarg_nodes, kwarg_locs)], starargs=None, kwargs=None, - type=types.TVar(), + type=types.TVar(), iodelay=None, begin_loc=begin_loc, end_loc=end_loc, star_loc=None, dstar_loc=None, loc=name_loc.join(end_loc)) diff --git a/artiq/compiler/ir.py b/artiq/compiler/ir.py index aa428f9b9..60126b534 100644 --- a/artiq/compiler/ir.py +++ b/artiq/compiler/ir.py @@ -1188,6 +1188,43 @@ class LandingPad(Terminator): 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 expr: (:class:`iodelay.Expr`) expression + :ivar var_names: (list of string) + iodelay expression names corresponding to operands + """ + + """ + :param expr: (:class:`iodelay.Expr`) expression + :param substs: (dict of str to :class:`Value`) + :param target: (:class:`BasicBlock`) branch target + """ + def __init__(self, expr, substs, target, name=""): + for var_name in substs: assert isinstance(var_name, str) + assert isinstance(target, BasicBlock) + super().__init__([target, *substs.values()], builtins.TNone(), name) + self.expr = expr + self.var_names = list(substs.keys()) + + def target(self): + return self.operands[0] + + def substs(self): + return zip(self.var_names, self.operands[1:]) + + def _operands_as_string(self, type_printer): + substs = [] + for var_name, operand in self.substs(): + substs.append("{}={}".format(var_name, operand)) + return "[{}], {}".format(", ".join(substs), self.target().as_operand(type_printer)) + + def opcode(self): + return "delay({})".format(self.expr) + class Parallel(Terminator): """ An instruction that schedules several threads of execution diff --git a/artiq/compiler/transforms/artiq_ir_generator.py b/artiq/compiler/transforms/artiq_ir_generator.py index f73295d20..4ae04a366 100644 --- a/artiq/compiler/transforms/artiq_ir_generator.py +++ b/artiq/compiler/transforms/artiq_ir_generator.py @@ -44,6 +44,9 @@ class ARTIQIRGenerator(algorithm.Visitor): 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 @@ -91,6 +94,7 @@ class ARTIQIRGenerator(algorithm.Visitor): self.current_block = None self.current_env = None self.current_private_env = None + self.current_args = None self.current_assign = None self.current_assert_env = None self.current_assert_subexprs = None @@ -228,13 +232,19 @@ class ARTIQIRGenerator(algorithm.Visitor): env_arg = ir.EnvironmentArgument(self.current_env.type, "outerenv") + old_args, self.current_args = self.current_args, {} + args = [] for arg_name in typ.args: - args.append(ir.Argument(typ.args[arg_name], "arg." + arg_name)) + 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: - optargs.append(ir.Argument(ir.TOption(typ.optargs[arg_name]), "arg." + arg_name)) + arg = ir.Argument(ir.TOption(typ.optargs[arg_name]), "arg." + arg_name) + self.current_args[arg_name] = arg + optargs.append(arg) func = ir.Function(typ, ".".join(self.name), [env_arg] + args + optargs, loc=node.lambda_loc if is_lambda else node.keyword_loc) @@ -284,6 +294,7 @@ class ARTIQIRGenerator(algorithm.Visitor): 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 @@ -1544,7 +1555,7 @@ class ARTIQIRGenerator(algorithm.Visitor): typ = node.func.type.find() if types.is_builtin(typ): - return self.visit_builtin_call(node) + insn = self.visit_builtin_call(node) else: if types.is_function(typ): func = self.visit(node.func) @@ -1557,6 +1568,8 @@ class ARTIQIRGenerator(algorithm.Visitor): self_arg = self.append(ir.GetAttr(method, "__self__")) fn_typ = types.get_method_function(typ) offset = 1 + else: + assert False args = [None] * (len(fn_typ.args) + len(fn_typ.optargs)) @@ -1606,7 +1619,15 @@ class ARTIQIRGenerator(algorithm.Visitor): attr_node = node.func self.method_map[(attr_node.value.type, attr_node.attr)].append(insn) - return insn + if node.iodelay is not None: + after_delay = self.add_block() + self.append(ir.Delay(node.iodelay, + {var_name: self.current_args[var_name] + for var_name in node.iodelay.free_vars()}, + after_delay)) + self.current_block = after_delay + + return insn def visit_QuoteT(self, node): return self.append(ir.Quote(node.value, node.type)) diff --git a/artiq/compiler/transforms/asttyped_rewriter.py b/artiq/compiler/transforms/asttyped_rewriter.py index 084b8a7ca..e8f811423 100644 --- a/artiq/compiler/transforms/asttyped_rewriter.py +++ b/artiq/compiler/transforms/asttyped_rewriter.py @@ -426,7 +426,7 @@ class ASTTypedRewriter(algorithm.Transformer): def visit_Call(self, node): node = self.generic_visit(node) - node = asttyped.CallT(type=types.TVar(), + node = asttyped.CallT(type=types.TVar(), iodelay=None, 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, diff --git a/artiq/compiler/transforms/iodelay_estimator.py b/artiq/compiler/transforms/iodelay_estimator.py index cc3da45a1..d13f9a36a 100644 --- a/artiq/compiler/transforms/iodelay_estimator.py +++ b/artiq/compiler/transforms/iodelay_estimator.py @@ -230,10 +230,10 @@ class IODelayEstimator(algorithm.Visitor): if types.is_builtin(typ, "delay"): value = self.evaluate(node.args[0], abort=abort) - self.current_delay += iodelay.SToMU(value, ref_period=self.ref_period) + 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) - self.current_delay += value + call_delay = value elif not types.is_builtin(typ): if types.is_function(typ): offset = 0 @@ -262,7 +262,10 @@ class IODelayEstimator(algorithm.Visitor): args[arg_name] = arg_node free_vars = delay.duration.free_vars() - self.current_delay += delay.duration.fold( + call_delay = delay.duration.fold( { arg: self.evaluate(args[arg], abort=abort) for arg in free_vars }) else: assert False + + self.current_delay += call_delay + node.iodelay = call_delay From 48a2bb10d5248b4aaec43be5b2aba4eeb57a5472 Mon Sep 17 00:00:00 2001 From: whitequark Date: Tue, 17 Nov 2015 05:22:20 +0300 Subject: [PATCH 328/369] transforms.interleaver: add boilerplate. --- artiq/compiler/module.py | 2 ++ artiq/compiler/transforms/__init__.py | 1 + artiq/compiler/transforms/interleaver.py | 22 ++++++++++++++++++++++ 3 files changed, 25 insertions(+) create mode 100644 artiq/compiler/transforms/interleaver.py diff --git a/artiq/compiler/module.py b/artiq/compiler/module.py index 0e607fa4d..03ddaae91 100644 --- a/artiq/compiler/module.py +++ b/artiq/compiler/module.py @@ -58,6 +58,7 @@ class Module: dead_code_eliminator = transforms.DeadCodeEliminator(engine=self.engine) local_access_validator = validators.LocalAccessValidator(engine=self.engine) devirtualization = analyses.Devirtualization() + interleaver = transforms.Interleaver(engine=self.engine) self.name = src.name self.globals = src.globals @@ -71,6 +72,7 @@ class Module: artiq_ir_generator.annotate_calls(devirtualization) dead_code_eliminator.process(self.artiq_ir) local_access_validator.process(self.artiq_ir) + interleaver.process(self.artiq_ir) def build_llvm_ir(self, target): """Compile the module to LLVM IR for the specified target.""" diff --git a/artiq/compiler/transforms/__init__.py b/artiq/compiler/transforms/__init__.py index 39831fea3..665fd3ea1 100644 --- a/artiq/compiler/transforms/__init__.py +++ b/artiq/compiler/transforms/__init__.py @@ -5,3 +5,4 @@ from .iodelay_estimator import IODelayEstimator from .artiq_ir_generator import ARTIQIRGenerator from .dead_code_eliminator import DeadCodeEliminator from .llvm_ir_generator import LLVMIRGenerator +from .interleaver import Interleaver diff --git a/artiq/compiler/transforms/interleaver.py b/artiq/compiler/transforms/interleaver.py new file mode 100644 index 000000000..256c5508d --- /dev/null +++ b/artiq/compiler/transforms/interleaver.py @@ -0,0 +1,22 @@ +""" +:class:`Interleaver` reorders requests to the RTIO core so that +the timestamp would always monotonically nondecrease. +""" + +from .. import ir +from ..analyses import domination + +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): + domtree = domination.PostDominatorTree(func) + print(func) + for block in func.basic_blocks: + idom = domtree.immediate_dominator(block) + print(block.name, "->", idom.name if idom else "") From 58db347e010745e4b0e778dab7b1a2a86a15524f Mon Sep 17 00:00:00 2001 From: whitequark Date: Thu, 19 Nov 2015 23:23:39 +0800 Subject: [PATCH 329/369] transforms.iodelay_estimator: fix uninitialized access. --- artiq/compiler/transforms/iodelay_estimator.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/artiq/compiler/transforms/iodelay_estimator.py b/artiq/compiler/transforms/iodelay_estimator.py index d13f9a36a..b530a1d90 100644 --- a/artiq/compiler/transforms/iodelay_estimator.py +++ b/artiq/compiler/transforms/iodelay_estimator.py @@ -266,6 +266,8 @@ class IODelayEstimator(algorithm.Visitor): { arg: self.evaluate(args[arg], abort=abort) for arg in free_vars }) else: assert False + else: + call_delay = iodelay.Const(0) self.current_delay += call_delay node.iodelay = call_delay From 2543daa5cfbb9784ce2d1d4b9cbef74847273f7e Mon Sep 17 00:00:00 2001 From: whitequark Date: Thu, 19 Nov 2015 23:24:46 +0800 Subject: [PATCH 330/369] transforms.artiq_ir_generator: don't emit delay instruction for zero delay. Call nodes with iodelay=Const(0) can be generated outside of `with parallel:`, where Interleaver won't and LLVMIRGenerator can't lower them. --- artiq/compiler/transforms/artiq_ir_generator.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/artiq/compiler/transforms/artiq_ir_generator.py b/artiq/compiler/transforms/artiq_ir_generator.py index 4ae04a366..9210be8b8 100644 --- a/artiq/compiler/transforms/artiq_ir_generator.py +++ b/artiq/compiler/transforms/artiq_ir_generator.py @@ -8,7 +8,7 @@ semantics explicitly. from collections import OrderedDict, defaultdict from pythonparser import algorithm, diagnostic, ast -from .. import types, builtins, asttyped, ir +from .. import types, builtins, asttyped, ir, iodelay def _readable_name(insn): if isinstance(insn, ir.Constant): @@ -1619,7 +1619,7 @@ class ARTIQIRGenerator(algorithm.Visitor): attr_node = node.func self.method_map[(attr_node.value.type, attr_node.attr)].append(insn) - if node.iodelay is not None: + if node.iodelay is not None and not iodelay.is_const(node.iodelay, 0): after_delay = self.add_block() self.append(ir.Delay(node.iodelay, {var_name: self.current_args[var_name] From a04d0f8fbd69ed586f93bc7dfc1cf2f4e05f4303 Mon Sep 17 00:00:00 2001 From: whitequark Date: Thu, 19 Nov 2015 23:26:42 +0800 Subject: [PATCH 331/369] lit-test: fix inferencer/error_class test (broken in b0c6b70). --- lit-test/test/inferencer/error_class.py | 6 ------ lit-test/test/inferencer/error_class_redefine.py | 8 ++++++++ 2 files changed, 8 insertions(+), 6 deletions(-) create mode 100644 lit-test/test/inferencer/error_class_redefine.py diff --git a/lit-test/test/inferencer/error_class.py b/lit-test/test/inferencer/error_class.py index dbb399494..0cc075b05 100644 --- a/lit-test/test/inferencer/error_class.py +++ b/lit-test/test/inferencer/error_class.py @@ -8,9 +8,3 @@ class a(1): class b: # CHECK-L: ${LINE:+1}: fatal: class body must contain only assignments and function definitions x += 1 - -class c: - pass -# CHECK-L: ${LINE:+1}: fatal: variable 'c' is already defined -class c: - pass diff --git a/lit-test/test/inferencer/error_class_redefine.py b/lit-test/test/inferencer/error_class_redefine.py new file mode 100644 index 000000000..d5556fd98 --- /dev/null +++ b/lit-test/test/inferencer/error_class_redefine.py @@ -0,0 +1,8 @@ +# 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 From b9bb5fba6a1dc3d0597981b5ec00159fcfaf4be4 Mon Sep 17 00:00:00 2001 From: whitequark Date: Thu, 19 Nov 2015 23:27:47 +0800 Subject: [PATCH 332/369] lit-test: fix iodelay/class test (broken in 506725f). --- lit-test/test/iodelay/class.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lit-test/test/iodelay/class.py b/lit-test/test/iodelay/class.py index 32580c4c6..61ca122f8 100644 --- a/lit-test/test/iodelay/class.py +++ b/lit-test/test/iodelay/class.py @@ -1,7 +1,7 @@ # RUN: %python -m artiq.compiler.testbench.signature %s >%t # RUN: OutputCheck %s --file-to-check=%t -# CHECK-L: g: ()->NoneType delay(s->mu(1.0) mu) +# CHECK-L: g: (i:)->NoneType delay(s->mu(1.0) mu) def g(i): i.f(1.0) From 9639a831bcade783dca88ca39bc0adfabe99f96f Mon Sep 17 00:00:00 2001 From: whitequark Date: Thu, 19 Nov 2015 23:44:39 +0800 Subject: [PATCH 333/369] transforms.artiq_ir_generator: correctly emit IfExpT with control flow. This can happen with nested if expressions, as well as if the if expression includes delays. --- artiq/compiler/transforms/artiq_ir_generator.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/artiq/compiler/transforms/artiq_ir_generator.py b/artiq/compiler/transforms/artiq_ir_generator.py index 9210be8b8..6a9c0c34a 100644 --- a/artiq/compiler/transforms/artiq_ir_generator.py +++ b/artiq/compiler/transforms/artiq_ir_generator.py @@ -729,23 +729,25 @@ class ARTIQIRGenerator(algorithm.Visitor): if_true = self.add_block() self.current_block = if_true true_result = self.visit(node.body) + post_if_true = self.current_block if_false = self.add_block() self.current_block = if_false false_result = 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 if_false.is_terminated(): - if_false.append(ir.Branch(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, if_true) - phi.add_incoming(false_result, if_false) + phi.add_incoming(true_result, post_if_true) + phi.add_incoming(false_result, post_if_false) return phi def visit_NumT(self, node): From 025bfbe7464411d0f01d1bf2c710f94b2d3cb3d1 Mon Sep 17 00:00:00 2001 From: whitequark Date: Thu, 19 Nov 2015 23:55:52 +0800 Subject: [PATCH 334/369] transforms.llvm_ir_generator: accept delay instructions. The delay instruction is just like a branch (discontinuity in instruction flow), but it also carries metadata: how long did the execution of its basic block take. This metadata only matters during inlining and interleaving, so we treat it here as a mere branch. --- artiq/compiler/transforms/llvm_ir_generator.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/artiq/compiler/transforms/llvm_ir_generator.py b/artiq/compiler/transforms/llvm_ir_generator.py index 5558265e4..d7f1df13a 100644 --- a/artiq/compiler/transforms/llvm_ir_generator.py +++ b/artiq/compiler/transforms/llvm_ir_generator.py @@ -1068,6 +1068,8 @@ class LLVMIRGenerator: 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())) From 00ec574d73e0957a2c96a3a3c137ced8e072b18b Mon Sep 17 00:00:00 2001 From: whitequark Date: Fri, 20 Nov 2015 00:03:26 +0800 Subject: [PATCH 335/369] transforms.interleaver: implement (without inlining). --- artiq/compiler/ir.py | 6 +++ artiq/compiler/transforms/interleaver.py | 69 +++++++++++++++++++++--- lit-test/test/interleaving/basic.py | 22 ++++++++ 3 files changed, 91 insertions(+), 6 deletions(-) create mode 100644 lit-test/test/interleaving/basic.py diff --git a/artiq/compiler/ir.py b/artiq/compiler/ir.py index 60126b534..adae44a89 100644 --- a/artiq/compiler/ir.py +++ b/artiq/compiler/ir.py @@ -953,6 +953,9 @@ class Branch(Terminator): def target(self): return self.operands[0] + def set_target(self, new_target): + self.operands[0] = new_target + class BranchIf(Terminator): """ A conditional branch instruction. @@ -1213,6 +1216,9 @@ class Delay(Terminator): def target(self): return self.operands[0] + def set_target(self, new_target): + self.operands[0] = new_target + def substs(self): return zip(self.var_names, self.operands[1:]) diff --git a/artiq/compiler/transforms/interleaver.py b/artiq/compiler/transforms/interleaver.py index 256c5508d..44dd4fc8c 100644 --- a/artiq/compiler/transforms/interleaver.py +++ b/artiq/compiler/transforms/interleaver.py @@ -3,7 +3,7 @@ the timestamp would always monotonically nondecrease. """ -from .. import ir +from .. import ir, iodelay from ..analyses import domination class Interleaver: @@ -15,8 +15,65 @@ class Interleaver: self.process_function(func) def process_function(self, func): - domtree = domination.PostDominatorTree(func) - print(func) - for block in func.basic_blocks: - idom = domtree.immediate_dominator(block) - print(block.name, "->", idom.name if idom else "") + for insn in func.instructions(): + if isinstance(insn, ir.Delay): + if any(insn.expr.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 isinstance(insn, ir.Parallel): + # Lazily compute dominators. + if postdom_tree is None: + postdom_tree = domination.PostDominatorTree(func) + + interleave_until = postdom_tree.immediate_dominator(insn.basic_block) + assert (interleave_until is not None) # no nonlocal flow in `with parallel` + + target_block = insn.basic_block + target_time = 0 + source_blocks = insn.basic_block.successors() + source_times = [0 for _ in source_blocks] + + while len(source_blocks) > 0: + def iodelay_of_block(block): + terminator = block.terminator() + if isinstance(terminator, ir.Delay): + # We should be able to fold everything without free variables. + assert iodelay.is_const(terminator.expr) + return terminator.expr.value + else: + return 0 + + def time_after_block(pair): + index, block = pair + return source_times[index] + iodelay_of_block(block) + + index, source_block = min(enumerate(source_blocks), key=time_after_block) + source_block_delay = iodelay_of_block(source_block) + + target_terminator = target_block.terminator() + if isinstance(target_terminator, (ir.Delay, ir.Branch)): + target_terminator.set_target(source_block) + elif isinstance(target_terminator, ir.Parallel): + target_terminator.replace_with(ir.Branch(source_block)) + else: + assert False + + new_source_block = postdom_tree.immediate_dominator(source_block) + assert (new_source_block is not None) + + target_block = source_block + target_time += source_block_delay + + 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] = target_time diff --git a/lit-test/test/interleaving/basic.py b/lit-test/test/interleaving/basic.py new file mode 100644 index 000000000..fbc9f2091 --- /dev/null +++ b/lit-test/test/interleaving/basic.py @@ -0,0 +1,22 @@ +# RUN: %python -m artiq.compiler.testbench.jit %s >%t +# RUN: OutputCheck %s --file-to-check=%t + +def g(): + with parallel: + 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: C 0 +# CHECK-L: A 2 +# CHECK-L: D 5 +# CHECK-L: B 7 +# CHECK-L: E 7 +g() From 88b79907145d8ec3dcb3a28aabe3b7d399528fda Mon Sep 17 00:00:00 2001 From: whitequark Date: Fri, 20 Nov 2015 17:10:25 +0800 Subject: [PATCH 336/369] transforms.iodelay_estimator: fail statements with indeterminate delay inside `with parallel`. --- .../compiler/transforms/iodelay_estimator.py | 42 ++++++++++++------- lit-test/test/iodelay/error_bad_parallel.py | 8 ++++ 2 files changed, 35 insertions(+), 15 deletions(-) create mode 100644 lit-test/test/iodelay/error_bad_parallel.py diff --git a/artiq/compiler/transforms/iodelay_estimator.py b/artiq/compiler/transforms/iodelay_estimator.py index b530a1d90..21d2d9616 100644 --- a/artiq/compiler/transforms/iodelay_estimator.py +++ b/artiq/compiler/transforms/iodelay_estimator.py @@ -10,6 +10,10 @@ 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 @@ -69,7 +73,7 @@ class IODelayEstimator(algorithm.Visitor): def abort(self, message, loc, notes=[]): diag = diagnostic.Diagnostic("error", message, {}, loc, notes=notes) - raise diagnostic.Error(diag) + raise _IndeterminateDelay(diag) def visit_fixpoint(self, node): while True: @@ -85,7 +89,7 @@ class IODelayEstimator(algorithm.Visitor): self.visit(stmt) except _UnknownDelay: pass # more luck next time? - except diagnostic.Error: + except _IndeterminateDelay: pass # we don't care; module-level code is never interleaved def visit_function(self, args, body, typ, loc): @@ -99,8 +103,8 @@ class IODelayEstimator(algorithm.Visitor): "can be interleaved", self.current_return.loc) delay = types.TFixedDelay(self.current_delay.fold()) - except diagnostic.Error as error: - delay = types.TIndeterminateDelay(error.diagnostic) + except _IndeterminateDelay as error: + delay = types.TIndeterminateDelay(error.cause) self.current_delay = old_delay self.current_return = old_return self.current_args = old_args @@ -208,14 +212,21 @@ class IODelayEstimator(algorithm.Visitor): context_expr = node.items[0].context_expr if len(node.items) == 1 and types.is_builtin(context_expr.type, "parallel"): - 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 + 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 + + 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. + self.engine.process(error.cause) - self.current_delay += iodelay.Max(delays) elif len(node.items) == 1 and types.is_builtin(context_expr.type, "sequential"): self.visit(node.body) else: @@ -247,13 +258,14 @@ class IODelayEstimator(algorithm.Visitor): if types.is_var(delay): raise _UnknownDelay() elif delay.is_indeterminate(): - cause = delay.cause note = diagnostic.Diagnostic("note", "function called here", {}, node.loc) - diag = diagnostic.Diagnostic(cause.level, cause.reason, cause.arguments, - cause.location, cause.highlights, cause.notes + [note]) - raise diagnostic.Error(diag) + 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: diff --git a/lit-test/test/iodelay/error_bad_parallel.py b/lit-test/test/iodelay/error_bad_parallel.py new file mode 100644 index 000000000..8a9a462b2 --- /dev/null +++ b/lit-test/test/iodelay/error_bad_parallel.py @@ -0,0 +1,8 @@ +# RUN: %python -m artiq.compiler.testbench.signature +diag %s >%t +# RUN: OutputCheck %s --file-to-check=%t + +def f(): + with parallel: + # CHECK-L: ${LINE:+1}: error: while statement cannot be interleaved + while True: + delay_mu(1) From d0f86e05d0839908a51c7d5b317ae2173bfe6d1f Mon Sep 17 00:00:00 2001 From: whitequark Date: Fri, 20 Nov 2015 17:27:04 +0800 Subject: [PATCH 337/369] transforms.interleaver: add safety check. --- artiq/compiler/transforms/interleaver.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/artiq/compiler/transforms/interleaver.py b/artiq/compiler/transforms/interleaver.py index 44dd4fc8c..ea2097ad2 100644 --- a/artiq/compiler/transforms/interleaver.py +++ b/artiq/compiler/transforms/interleaver.py @@ -6,6 +6,25 @@ the timestamp would always monotonically nondecrease. from .. import ir, iodelay from ..analyses import domination +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 + class Interleaver: def __init__(self, engine): self.engine = engine @@ -66,6 +85,7 @@ class Interleaver: 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) target_block = source_block target_time += source_block_delay From cc623c13b4bfbd339817a685bfe2d6032cf173ad Mon Sep 17 00:00:00 2001 From: whitequark Date: Fri, 20 Nov 2015 21:41:44 +0800 Subject: [PATCH 338/369] Reformat. --- artiq/compiler/transforms/interleaver.py | 90 ++++++++++++------------ 1 file changed, 46 insertions(+), 44 deletions(-) diff --git a/artiq/compiler/transforms/interleaver.py b/artiq/compiler/transforms/interleaver.py index ea2097ad2..f737d839c 100644 --- a/artiq/compiler/transforms/interleaver.py +++ b/artiq/compiler/transforms/interleaver.py @@ -45,55 +45,57 @@ class Interleaver: postdom_tree = None for insn in func.instructions(): - if isinstance(insn, ir.Parallel): - # Lazily compute dominators. - if postdom_tree is None: - postdom_tree = domination.PostDominatorTree(func) + if not isinstance(insn, ir.Parallel): + continue - interleave_until = postdom_tree.immediate_dominator(insn.basic_block) - assert (interleave_until is not None) # no nonlocal flow in `with parallel` + # 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] + interleave_until = postdom_tree.immediate_dominator(insn.basic_block) + assert (interleave_until is not None) # no nonlocal flow in `with parallel` - while len(source_blocks) > 0: - def iodelay_of_block(block): - terminator = block.terminator() - if isinstance(terminator, ir.Delay): - # We should be able to fold everything without free variables. - assert iodelay.is_const(terminator.expr) - return terminator.expr.value - else: - return 0 + target_block = insn.basic_block + target_time = 0 + source_blocks = insn.basic_block.successors() + source_times = [0 for _ in source_blocks] - def time_after_block(pair): - index, block = pair - return source_times[index] + iodelay_of_block(block) - - index, source_block = min(enumerate(source_blocks), key=time_after_block) - source_block_delay = iodelay_of_block(source_block) - - target_terminator = target_block.terminator() - if isinstance(target_terminator, (ir.Delay, ir.Branch)): - target_terminator.set_target(source_block) - elif isinstance(target_terminator, ir.Parallel): - target_terminator.replace_with(ir.Branch(source_block)) + while len(source_blocks) > 0: + def iodelay_of_block(block): + terminator = block.terminator() + if isinstance(terminator, ir.Delay): + # We should be able to fold everything without free variables. + assert iodelay.is_const(terminator.expr) + return terminator.expr.value else: - assert False + return 0 - 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) + def time_after_block(pair): + index, block = pair + return source_times[index] + iodelay_of_block(block) - target_block = source_block - target_time += source_block_delay + index, source_block = min(enumerate(source_blocks), key=time_after_block) + source_block_delay = iodelay_of_block(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] = target_time + target_terminator = target_block.terminator() + if isinstance(target_terminator, (ir.Delay, ir.Branch)): + target_terminator.set_target(source_block) + elif isinstance(target_terminator, ir.Parallel): + target_terminator.replace_with(ir.Branch(source_block)) + else: + assert False + + target_block = source_block + target_time += source_block_delay + + 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] = target_time From cb9e7d15bf2a23d551702504ef4610883cf70c9b Mon Sep 17 00:00:00 2001 From: whitequark Date: Fri, 20 Nov 2015 22:15:03 +0800 Subject: [PATCH 339/369] compiler.iodelay: subtraction certainly shouldn't be commutative. --- artiq/compiler/iodelay.py | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/artiq/compiler/iodelay.py b/artiq/compiler/iodelay.py index ec9a73729..47e04472a 100644 --- a/artiq/compiler/iodelay.py +++ b/artiq/compiler/iodelay.py @@ -158,18 +158,23 @@ class Add(BinOpFixpoint): _op = lambda a, b: a + b _fixpoint = 0 -class Sub(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: From 73c358a59a9e5b62195843cae5e5d2604457e951 Mon Sep 17 00:00:00 2001 From: whitequark Date: Fri, 20 Nov 2015 23:33:06 +0800 Subject: [PATCH 340/369] Reformat. --- .../compiler/transforms/artiq_ir_generator.py | 119 ++++++++++-------- 1 file changed, 65 insertions(+), 54 deletions(-) diff --git a/artiq/compiler/transforms/artiq_ir_generator.py b/artiq/compiler/transforms/artiq_ir_generator.py index 6a9c0c34a..72f00ebe8 100644 --- a/artiq/compiler/transforms/artiq_ir_generator.py +++ b/artiq/compiler/transforms/artiq_ir_generator.py @@ -1558,68 +1558,79 @@ class ARTIQIRGenerator(algorithm.Visitor): if types.is_builtin(typ): insn = self.visit_builtin_call(node) + + # Temporary. + if node.iodelay is not None and not iodelay.is_const(node.iodelay, 0): + after_delay = self.add_block() + self.append(ir.Delay(node.iodelay, + {var_name: self.current_args[var_name] + for var_name in node.iodelay.free_vars()}, + after_delay)) + self.current_block = after_delay + + return insn + + if types.is_function(typ): + func = self.visit(node.func) + self_arg = None + fn_typ = typ + offset = 0 + elif types.is_method(typ): + method = self.visit(node.func) + func = self.append(ir.GetAttr(method, "__func__")) + self_arg = self.append(ir.GetAttr(method, "__self__")) + fn_typ = types.get_method_function(typ) + offset = 1 else: - if types.is_function(typ): - func = self.visit(node.func) - self_arg = None - fn_typ = typ - offset = 0 - elif types.is_method(typ): - method = self.visit(node.func) - func = self.append(ir.GetAttr(method, "__func__")) - self_arg = self.append(ir.GetAttr(method, "__self__")) - fn_typ = types.get_method_function(typ) - offset = 1 + assert False + + args = [None] * (len(fn_typ.args) + len(fn_typ.optargs)) + + for index, arg_node in enumerate(node.args): + arg = self.visit(arg_node) + if index < len(fn_typ.args): + args[index + offset] = arg else: - assert False + args[index + offset] = self.append(ir.Alloc([arg], ir.TOption(arg.type))) - args = [None] * (len(fn_typ.args) + len(fn_typ.optargs)) + for keyword in node.keywords: + arg = self.visit(keyword.value) + if keyword.arg in fn_typ.args: + for index, arg_name in enumerate(fn_typ.args): + if keyword.arg == arg_name: + assert args[index] is None + args[index] = arg + break + elif keyword.arg in fn_typ.optargs: + for index, optarg_name in enumerate(fn_typ.optargs): + if keyword.arg == 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, arg_node in enumerate(node.args): - arg = self.visit(arg_node) - if index < len(fn_typ.args): - args[index + offset] = arg - else: - args[index + offset] = self.append(ir.Alloc([arg], ir.TOption(arg.type))) + 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]))) - for keyword in node.keywords: - arg = self.visit(keyword.value) - if keyword.arg in fn_typ.args: - for index, arg_name in enumerate(fn_typ.args): - if keyword.arg == arg_name: - assert args[index] is None - args[index] = arg - break - elif keyword.arg in fn_typ.optargs: - for index, optarg_name in enumerate(fn_typ.optargs): - if keyword.arg == 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 + if self_arg is not None: + assert args[0] is None + args[0] = self_arg - 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]))) + assert None not in args - if self_arg is not None: - assert args[0] is None - args[0] = self_arg + if self.unwind_target is None: + insn = self.append(ir.Call(func, args)) + else: + after_invoke = self.add_block() + insn = self.append(ir.Invoke(func, args, after_invoke, self.unwind_target)) + self.current_block = after_invoke - assert None not in args - - if self.unwind_target is None: - insn = self.append(ir.Call(func, args)) - else: - after_invoke = self.add_block() - insn = self.append(ir.Invoke(func, args, after_invoke, self.unwind_target)) - self.current_block = after_invoke - - method_key = None - if isinstance(node.func, asttyped.AttributeT): - attr_node = node.func - self.method_map[(attr_node.value.type, attr_node.attr)].append(insn) + method_key = None + if isinstance(node.func, asttyped.AttributeT): + attr_node = node.func + self.method_map[(attr_node.value.type, attr_node.attr)].append(insn) if node.iodelay is not None and not iodelay.is_const(node.iodelay, 0): after_delay = self.add_block() From 50e7b44d04713b2091e5e76ac1384f2064c3bea4 Mon Sep 17 00:00:00 2001 From: whitequark Date: Sat, 21 Nov 2015 00:02:47 +0800 Subject: [PATCH 341/369] compiler: actually implement interleaving correctly (calls are still broken). The previous implementation was completely wrong: it always advanced the global timeline by the same amount as the non-interleaved basic block did. The new implementation only advances the global timeline by the difference between its current time and the virtual time of the branch, which requires it to adjust the delay instructions. Previously, the delay expression was present in the IR twice: once as the iodelay.Expr transformation-visible form, and once as regular IR instructions, with the latter form being passed to the delay_mu builtin and advancing the runtime timeline. As a result of this change, this strategy is no longer valid: we can meaningfully mutate the iodelay.Expr form but not the IR instruction form. Thus, IR instructions are no longer generated for delay expressions, and the LLVM lowering pass now has to lower the iodelay.Expr objects as well. This works OK for flat `with parallel:` expressions, but breaks down outside of `with parallel:` or when calls are present. The reasons it breaks down are as follows: * Outside of `with parallel:`, delay() and delay_mu() must accept any expression, but iodelay.Expr's are not nearly expressive enough. So, the IR instruction form must actually be kept as well. * A delay instruction is currently inserted after a call to a user-defined function; this delay instruction introduces a point where basic block reordering is possible as well as provides delay information. However, the callee knows nothing about the context in which it is called, which means that the runtime timeline is advanced twice. So, a new terminator instruction must be added that combines the properties of delay and call instructions (and another for delay and invoke as well). --- .../compiler/transforms/artiq_ir_generator.py | 30 +++++++++---------- artiq/compiler/transforms/interleaver.py | 16 ++++++++-- .../compiler/transforms/llvm_ir_generator.py | 23 ++++++++------ lit-test/test/interleaving/nonoverlapping.py | 25 ++++++++++++++++ .../interleaving/{basic.py => overlapping.py} | 9 ++++-- 5 files changed, 73 insertions(+), 30 deletions(-) create mode 100644 lit-test/test/interleaving/nonoverlapping.py rename lit-test/test/interleaving/{basic.py => overlapping.py} (83%) diff --git a/artiq/compiler/transforms/artiq_ir_generator.py b/artiq/compiler/transforms/artiq_ir_generator.py index 72f00ebe8..007b2b7b2 100644 --- a/artiq/compiler/transforms/artiq_ir_generator.py +++ b/artiq/compiler/transforms/artiq_ir_generator.py @@ -1418,6 +1418,15 @@ class ARTIQIRGenerator(algorithm.Visitor): return self.append(ir.Alloc(attributes, typ)) + def _make_delay(self, delay): + if not iodelay.is_const(delay, 0): + after_delay = self.add_block() + self.append(ir.Delay(delay, + {var_name: self.current_args[var_name] + for var_name in delay.free_vars()}, + after_delay)) + self.current_block = after_delay + def visit_builtin_call(self, node): # A builtin by any other name... Ignore node.func, just use the type. typ = node.func.type @@ -1520,7 +1529,7 @@ class ARTIQIRGenerator(algorithm.Visitor): return self.append(ir.Arith(ast.Mult(loc=None), now_mu_float, self.ref_period)) else: assert False - elif types.is_builtin(typ, "delay") or types.is_builtin(typ, "at"): + elif types.is_builtin(typ, "at"): 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)) @@ -1528,8 +1537,7 @@ class ARTIQIRGenerator(algorithm.Visitor): self.append(ir.Builtin(typ.name + "_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"): + elif types.is_builtin(typ, "now_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_builtin(typ, "mu_to_seconds"): @@ -1546,6 +1554,9 @@ class ARTIQIRGenerator(algorithm.Visitor): return self.append(ir.Coerce(arg_mu, builtins.TInt(types.TValue(64)))) else: assert False + elif types.is_builtin(typ, "delay") or types.is_builtin(typ, "delay_mu"): + assert node.iodelay is not None + self._make_delay(node.iodelay) 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): @@ -1557,18 +1568,7 @@ class ARTIQIRGenerator(algorithm.Visitor): typ = node.func.type.find() if types.is_builtin(typ): - insn = self.visit_builtin_call(node) - - # Temporary. - if node.iodelay is not None and not iodelay.is_const(node.iodelay, 0): - after_delay = self.add_block() - self.append(ir.Delay(node.iodelay, - {var_name: self.current_args[var_name] - for var_name in node.iodelay.free_vars()}, - after_delay)) - self.current_block = after_delay - - return insn + return self.visit_builtin_call(node) if types.is_function(typ): func = self.visit(node.func) diff --git a/artiq/compiler/transforms/interleaver.py b/artiq/compiler/transforms/interleaver.py index f737d839c..5327dca6b 100644 --- a/artiq/compiler/transforms/interleaver.py +++ b/artiq/compiler/transforms/interleaver.py @@ -77,6 +77,9 @@ class Interleaver: index, source_block = min(enumerate(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 + target_terminator = target_block.terminator() if isinstance(target_terminator, (ir.Delay, ir.Branch)): target_terminator.set_target(source_block) @@ -85,8 +88,15 @@ class Interleaver: else: assert False - target_block = source_block - target_time += source_block_delay + source_terminator = source_block.terminator() + if target_time_delta > 0: + assert isinstance(source_terminator, ir.Delay) + source_terminator.expr = iodelay.Const(target_time_delta) + else: + source_terminator.replace_with(ir.Branch(source_terminator.target())) + + 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) @@ -98,4 +108,4 @@ class Interleaver: del source_times[index] else: source_blocks[index] = new_source_block - source_times[index] = target_time + source_times[index] = new_target_time diff --git a/artiq/compiler/transforms/llvm_ir_generator.py b/artiq/compiler/transforms/llvm_ir_generator.py index d7f1df13a..269365847 100644 --- a/artiq/compiler/transforms/llvm_ir_generator.py +++ b/artiq/compiler/transforms/llvm_ir_generator.py @@ -7,7 +7,7 @@ import os from pythonparser import ast, diagnostic from llvmlite_artiq import ir as ll from ...language import core as language_core -from .. import types, builtins, ir +from .. import types, builtins, ir, iodelay llvoid = ll.VoidType() @@ -784,12 +784,6 @@ class LLVMIRGenerator: return self.map(insn.operands[0]) elif insn.op == "now_mu": return self.llbuilder.load(self.llbuiltin("now"), name=insn.name) - elif insn.op == "delay_mu": - interval, = insn.operands - llnowptr = self.llbuiltin("now") - llnow = self.llbuilder.load(llnowptr) - lladjusted = self.llbuilder.add(llnow, self.map(interval)) - return self.llbuilder.store(lladjusted, llnowptr) elif insn.op == "at_mu": time, = insn.operands return self.llbuilder.store(self.map(time), self.llbuiltin("now")) @@ -1068,8 +1062,6 @@ class LLVMIRGenerator: 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())) @@ -1150,3 +1142,16 @@ class LLVMIRGenerator: return llexn + def process_Delay(self, insn): + def map_delay(expr): + if isinstance(expr, iodelay.Const): + return ll.Constant(lli64, int(expr.value)) + else: + assert False + + llnowptr = self.llbuiltin("now") + llnow = self.llbuilder.load(llnowptr) + lladjusted = self.llbuilder.add(llnow, map_delay(insn.expr)) + self.llbuilder.store(lladjusted, llnowptr) + + return self.llbuilder.branch(self.map(insn.target())) diff --git a/lit-test/test/interleaving/nonoverlapping.py b/lit-test/test/interleaving/nonoverlapping.py new file mode 100644 index 000000000..ca471046d --- /dev/null +++ b/lit-test/test/interleaving/nonoverlapping.py @@ -0,0 +1,25 @@ +# RUN: %python -m artiq.compiler.testbench.jit %s >%t +# RUN: OutputCheck %s --file-to-check=%t + +def g(): + with parallel: + 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: B 2 +# CHECK-L: C 2 +# CHECK-L: D 2 +# CHECK-L: E 4 +g() diff --git a/lit-test/test/interleaving/basic.py b/lit-test/test/interleaving/overlapping.py similarity index 83% rename from lit-test/test/interleaving/basic.py rename to lit-test/test/interleaving/overlapping.py index fbc9f2091..32dcfabce 100644 --- a/lit-test/test/interleaving/basic.py +++ b/lit-test/test/interleaving/overlapping.py @@ -6,17 +6,20 @@ def g(): 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: C 0 # CHECK-L: A 2 -# CHECK-L: D 5 -# CHECK-L: B 7 -# CHECK-L: E 7 +# CHECK-L: B 3 +# CHECK-L: D 3 +# CHECK-L: E 4 g() From cb3b811fd780e5f56d421a0e088552db2e3f3d42 Mon Sep 17 00:00:00 2001 From: whitequark Date: Sat, 21 Nov 2015 03:22:47 +0800 Subject: [PATCH 342/369] compiler: maintain both the IR and iodelay forms of delay expressions. After this commit, the delay instruction (again) does not generate any LLVM IR: all heavy lifting is relegated to the delay and delay_mu intrinsics. When the interleave transform needs to adjust the global timeline, it synthesizes a delay_mu intrinsnic. This way, the interleave transformation becomes composable, as the input and the output IR invariants are the same. Also, code generation is adjusted so that a basic block is split off not only after a delay call, but also before one; otherwise, e.g., code immediately at the beginning of a `with parallel:` branch would have no choice but to execute after another branch has already advanced the timeline. This takes care of issue #1 described in 50e7b44 and is a step to solving issue #2. --- artiq/compiler/builtins.py | 6 + artiq/compiler/ir.py | 37 ++++- .../compiler/transforms/artiq_ir_generator.py | 140 +++++++++--------- artiq/compiler/transforms/interleaver.py | 31 +++- .../compiler/transforms/llvm_ir_generator.py | 24 ++- lit-test/test/interleaving/nonoverlapping.py | 2 +- lit-test/test/interleaving/overlapping.py | 4 +- 7 files changed, 139 insertions(+), 105 deletions(-) diff --git a/artiq/compiler/builtins.py b/artiq/compiler/builtins.py index e4a0c7257..99065d39b 100644 --- a/artiq/compiler/builtins.py +++ b/artiq/compiler/builtins.py @@ -38,6 +38,12 @@ class TInt(types.TMono): def one(): return 1 +def TInt32(): + return TInt(types.TValue(32)) + +def TInt64(): + return TInt(types.TValue(64)) + class TFloat(types.TMono): def __init__(self): super().__init__("float") diff --git a/artiq/compiler/ir.py b/artiq/compiler/ir.py index adae44a89..4d29e1529 100644 --- a/artiq/compiler/ir.py +++ b/artiq/compiler/ir.py @@ -72,6 +72,16 @@ class Constant(Value): 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. @@ -190,7 +200,7 @@ class Instruction(User): return ", ".join([operand.as_operand(type_printer) for operand in self.operands]) def as_entity(self, type_printer): - if builtins.is_none(self.type): + if builtins.is_none(self.type) and len(self.uses) == 0: prefix = "" else: prefix = "%{} = {} ".format(escape_name(self.name), @@ -1198,35 +1208,46 @@ class Delay(Terminator): :ivar expr: (:class:`iodelay.Expr`) expression :ivar var_names: (list of string) - iodelay expression names corresponding to operands + iodelay variable names corresponding to operands """ """ :param expr: (:class:`iodelay.Expr`) expression :param substs: (dict of str to :class:`Value`) + SSA values corresponding to iodelay variable names + :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, expr, substs, target, name=""): + def __init__(self, expr, substs, decomposition, target, name=""): for var_name in substs: assert isinstance(var_name, str) + assert isinstance(decomposition, Call) or \ + isinstance(decomposition, Builtin) and decomposition.op in ("delay", "delay_mu") assert isinstance(target, BasicBlock) - super().__init__([target, *substs.values()], builtins.TNone(), name) + super().__init__([decomposition, target, *substs.values()], builtins.TNone(), name) self.expr = expr self.var_names = list(substs.keys()) - def target(self): + def decomposition(self): return self.operands[0] + def target(self): + return self.operands[1] + def set_target(self, new_target): - self.operands[0] = new_target + self.operands[1] = new_target def substs(self): - return zip(self.var_names, self.operands[1:]) + return zip(self.var_names, self.operands[2:]) def _operands_as_string(self, type_printer): substs = [] for var_name, operand in self.substs(): substs.append("{}={}".format(var_name, operand)) - return "[{}], {}".format(", ".join(substs), self.target().as_operand(type_printer)) + result = "[{}]".format(", ".join(substs)) + 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.expr) diff --git a/artiq/compiler/transforms/artiq_ir_generator.py b/artiq/compiler/transforms/artiq_ir_generator.py index 007b2b7b2..a0349d6eb 100644 --- a/artiq/compiler/transforms/artiq_ir_generator.py +++ b/artiq/compiler/transforms/artiq_ir_generator.py @@ -1418,15 +1418,6 @@ class ARTIQIRGenerator(algorithm.Visitor): return self.append(ir.Alloc(attributes, typ)) - def _make_delay(self, delay): - if not iodelay.is_const(delay, 0): - after_delay = self.add_block() - self.append(ir.Delay(delay, - {var_name: self.current_args[var_name] - for var_name in delay.free_vars()}, - after_delay)) - self.current_block = after_delay - def visit_builtin_call(self, node): # A builtin by any other name... Ignore node.func, just use the type. typ = node.func.type @@ -1529,7 +1520,7 @@ class ARTIQIRGenerator(algorithm.Visitor): return self.append(ir.Arith(ast.Mult(loc=None), now_mu_float, self.ref_period)) else: assert False - elif types.is_builtin(typ, "at"): + elif types.is_builtin(typ, "delay") or types.is_builtin(typ, "at"): 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)) @@ -1537,7 +1528,8 @@ class ARTIQIRGenerator(algorithm.Visitor): self.append(ir.Builtin(typ.name + "_mu", [arg_mu], builtins.TNone())) else: assert False - elif types.is_builtin(typ, "now_mu") or types.is_builtin(typ, "at_mu"): + 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_builtin(typ, "mu_to_seconds"): @@ -1554,9 +1546,6 @@ class ARTIQIRGenerator(algorithm.Visitor): return self.append(ir.Coerce(arg_mu, builtins.TInt(types.TValue(64)))) else: assert False - elif types.is_builtin(typ, "delay") or types.is_builtin(typ, "delay_mu"): - assert node.iodelay is not None - self._make_delay(node.iodelay) 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): @@ -1567,77 +1556,82 @@ class ARTIQIRGenerator(algorithm.Visitor): def visit_CallT(self, node): typ = node.func.type.find() + if node.iodelay is not None and not iodelay.is_const(node.iodelay, 0): + before_delay = self.current_block + during_delay = self.add_block() + before_delay.append(ir.Branch(during_delay)) + self.current_block = during_delay + if types.is_builtin(typ): - return self.visit_builtin_call(node) - - if types.is_function(typ): - func = self.visit(node.func) - self_arg = None - fn_typ = typ - offset = 0 - elif types.is_method(typ): - method = self.visit(node.func) - func = self.append(ir.GetAttr(method, "__func__")) - self_arg = self.append(ir.GetAttr(method, "__self__")) - fn_typ = types.get_method_function(typ) - offset = 1 + insn = self.visit_builtin_call(node) else: - assert False - - args = [None] * (len(fn_typ.args) + len(fn_typ.optargs)) - - for index, arg_node in enumerate(node.args): - arg = self.visit(arg_node) - if index < len(fn_typ.args): - args[index + offset] = arg + if types.is_function(typ): + func = self.visit(node.func) + self_arg = None + fn_typ = typ + offset = 0 + elif types.is_method(typ): + method = self.visit(node.func) + func = self.append(ir.GetAttr(method, "__func__")) + self_arg = self.append(ir.GetAttr(method, "__self__")) + fn_typ = types.get_method_function(typ) + offset = 1 else: - args[index + offset] = self.append(ir.Alloc([arg], ir.TOption(arg.type))) + assert False - for keyword in node.keywords: - arg = self.visit(keyword.value) - if keyword.arg in fn_typ.args: - for index, arg_name in enumerate(fn_typ.args): - if keyword.arg == arg_name: - assert args[index] is None - args[index] = arg - break - elif keyword.arg in fn_typ.optargs: - for index, optarg_name in enumerate(fn_typ.optargs): - if keyword.arg == 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 + args = [None] * (len(fn_typ.args) + len(fn_typ.optargs)) - 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]))) + for index, arg_node in enumerate(node.args): + arg = self.visit(arg_node) + if index < len(fn_typ.args): + args[index + offset] = arg + else: + args[index + offset] = self.append(ir.Alloc([arg], ir.TOption(arg.type))) - if self_arg is not None: - assert args[0] is None - args[0] = self_arg + for keyword in node.keywords: + arg = self.visit(keyword.value) + if keyword.arg in fn_typ.args: + for index, arg_name in enumerate(fn_typ.args): + if keyword.arg == arg_name: + assert args[index] is None + args[index] = arg + break + elif keyword.arg in fn_typ.optargs: + for index, optarg_name in enumerate(fn_typ.optargs): + if keyword.arg == 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 - assert None not in args + 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.unwind_target is None: - insn = self.append(ir.Call(func, args)) - else: - after_invoke = self.add_block() - insn = self.append(ir.Invoke(func, args, after_invoke, self.unwind_target)) - self.current_block = after_invoke + if self_arg is not None: + assert args[0] is None + args[0] = self_arg - method_key = None - if isinstance(node.func, asttyped.AttributeT): - attr_node = node.func - self.method_map[(attr_node.value.type, attr_node.attr)].append(insn) + assert None not in args + + if self.unwind_target is None: + insn = self.append(ir.Call(func, args)) + else: + after_invoke = self.add_block() + insn = self.append(ir.Invoke(func, args, after_invoke, self.unwind_target)) + self.current_block = after_invoke + + method_key = None + if isinstance(node.func, asttyped.AttributeT): + attr_node = node.func + self.method_map[(attr_node.value.type, attr_node.attr)].append(insn) if node.iodelay is not None and not iodelay.is_const(node.iodelay, 0): after_delay = self.add_block() - self.append(ir.Delay(node.iodelay, - {var_name: self.current_args[var_name] - for var_name in node.iodelay.free_vars()}, - after_delay)) + substs = {var_name: self.current_args[var_name] + for var_name in node.iodelay.free_vars()} + self.append(ir.Delay(node.iodelay, substs, insn, after_delay)) self.current_block = after_delay return insn diff --git a/artiq/compiler/transforms/interleaver.py b/artiq/compiler/transforms/interleaver.py index 5327dca6b..23bf3d33d 100644 --- a/artiq/compiler/transforms/interleaver.py +++ b/artiq/compiler/transforms/interleaver.py @@ -3,7 +3,7 @@ the timestamp would always monotonically nondecrease. """ -from .. import ir, iodelay +from .. import types, builtins, ir, iodelay from ..analyses import domination def delay_free_subgraph(root, limit): @@ -81,20 +81,39 @@ class Interleaver: target_time_delta = new_target_time - target_time target_terminator = target_block.terminator() - if isinstance(target_terminator, (ir.Delay, ir.Branch)): - target_terminator.set_target(source_block) - elif isinstance(target_terminator, ir.Parallel): + if isinstance(target_terminator, ir.Parallel): target_terminator.replace_with(ir.Branch(source_block)) else: - assert False + assert isinstance(target_terminator, (ir.Delay, ir.Branch)) + target_terminator.set_target(source_block) source_terminator = source_block.terminator() + + if isinstance(source_terminator, ir.Delay): + old_decomp = source_terminator.decomposition() + else: + old_decomp = None + if target_time_delta > 0: assert isinstance(source_terminator, ir.Delay) - source_terminator.expr = iodelay.Const(target_time_delta) + + if isinstance(old_decomp, ir.Builtin) and \ + old_decomp.op in ("delay", "delay_mu"): + new_decomp_expr = ir.Constant(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(source_terminator, new_decomp) + else: + assert False # TODO + + source_terminator.replace_with(ir.Delay(iodelay.Const(target_time_delta), {}, + new_decomp, source_terminator.target())) else: source_terminator.replace_with(ir.Branch(source_terminator.target())) + if old_decomp is not None: + old_decomp.erase() + target_block = source_block target_time = new_target_time diff --git a/artiq/compiler/transforms/llvm_ir_generator.py b/artiq/compiler/transforms/llvm_ir_generator.py index 269365847..0df479acc 100644 --- a/artiq/compiler/transforms/llvm_ir_generator.py +++ b/artiq/compiler/transforms/llvm_ir_generator.py @@ -7,7 +7,7 @@ import os from pythonparser import ast, diagnostic from llvmlite_artiq import ir as ll from ...language import core as language_core -from .. import types, builtins, ir, iodelay +from .. import types, builtins, ir llvoid = ll.VoidType() @@ -787,6 +787,12 @@ class LLVMIRGenerator: elif insn.op == "at_mu": time, = insn.operands return self.llbuilder.store(self.map(time), self.llbuiltin("now")) + elif insn.op == "delay_mu": + interval, = insn.operands + llnowptr = self.llbuiltin("now") + llnow = self.llbuilder.load(llnowptr) + lladjusted = self.llbuilder.add(llnow, self.map(interval)) + return self.llbuilder.store(lladjusted, llnowptr) else: assert False @@ -1062,6 +1068,8 @@ class LLVMIRGenerator: 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())) @@ -1141,17 +1149,3 @@ class LLVMIRGenerator: self.llbuilder.branch(self.map(insn.cleanup())) return llexn - - def process_Delay(self, insn): - def map_delay(expr): - if isinstance(expr, iodelay.Const): - return ll.Constant(lli64, int(expr.value)) - else: - assert False - - llnowptr = self.llbuiltin("now") - llnow = self.llbuilder.load(llnowptr) - lladjusted = self.llbuilder.add(llnow, map_delay(insn.expr)) - self.llbuilder.store(lladjusted, llnowptr) - - return self.llbuilder.branch(self.map(insn.target())) diff --git a/lit-test/test/interleaving/nonoverlapping.py b/lit-test/test/interleaving/nonoverlapping.py index ca471046d..60b3f739e 100644 --- a/lit-test/test/interleaving/nonoverlapping.py +++ b/lit-test/test/interleaving/nonoverlapping.py @@ -18,8 +18,8 @@ def g(): print("E", now_mu()) # CHECK-L: A 0 +# CHECK-L: C 0 # CHECK-L: B 2 -# CHECK-L: C 2 # CHECK-L: D 2 # CHECK-L: E 4 g() diff --git a/lit-test/test/interleaving/overlapping.py b/lit-test/test/interleaving/overlapping.py index 32dcfabce..630fdb638 100644 --- a/lit-test/test/interleaving/overlapping.py +++ b/lit-test/test/interleaving/overlapping.py @@ -17,9 +17,9 @@ def g(): # print("E", now_mu()) +# CHECK-L: A 0 # CHECK-L: C 0 -# CHECK-L: A 2 +# CHECK-L: D 2 # CHECK-L: B 3 -# CHECK-L: D 3 # CHECK-L: E 4 g() From 57dd163d37e0cadb9f1515113d73ca7e60942662 Mon Sep 17 00:00:00 2001 From: whitequark Date: Sat, 21 Nov 2015 03:27:06 +0800 Subject: [PATCH 343/369] transforms.artiq_ir_generator: fix decomposition of explicit delay_mu(). --- artiq/compiler/transforms/artiq_ir_generator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/artiq/compiler/transforms/artiq_ir_generator.py b/artiq/compiler/transforms/artiq_ir_generator.py index a0349d6eb..376d3a48a 100644 --- a/artiq/compiler/transforms/artiq_ir_generator.py +++ b/artiq/compiler/transforms/artiq_ir_generator.py @@ -1525,7 +1525,7 @@ class ARTIQIRGenerator(algorithm.Visitor): 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.Coerce(arg_mu_float, builtins.TInt(types.TValue(64)))) - self.append(ir.Builtin(typ.name + "_mu", [arg_mu], builtins.TNone())) + return self.append(ir.Builtin(typ.name + "_mu", [arg_mu], builtins.TNone())) else: assert False elif types.is_builtin(typ, "now_mu") or types.is_builtin(typ, "delay_mu") \ From 82b470891fe1ba16567439e2739d1fd03822c3d5 Mon Sep 17 00:00:00 2001 From: whitequark Date: Sat, 21 Nov 2015 03:32:53 +0800 Subject: [PATCH 344/369] transforms.interleaver: handle function calls (as atomic so far). This commit solves issue #2 described in 50e7b44; a function call is now a valid decomposition for a delay instruction, and this metadata is propagated when the interleaver converts delays. However, the interleaver does not yet detect that a called function is compound, i.e. it is not correct. --- artiq/compiler/transforms/interleaver.py | 2 +- lit-test/test/interleaving/indirect.py | 28 ++++++++++++++++++++++ lit-test/test/interleaving/indirect_arg.py | 28 ++++++++++++++++++++++ 3 files changed, 57 insertions(+), 1 deletion(-) create mode 100644 lit-test/test/interleaving/indirect.py create mode 100644 lit-test/test/interleaving/indirect_arg.py diff --git a/artiq/compiler/transforms/interleaver.py b/artiq/compiler/transforms/interleaver.py index 23bf3d33d..1fc8b4e1e 100644 --- a/artiq/compiler/transforms/interleaver.py +++ b/artiq/compiler/transforms/interleaver.py @@ -104,7 +104,7 @@ class Interleaver: new_decomp.loc = old_decomp.loc source_terminator.basic_block.insert(source_terminator, new_decomp) else: - assert False # TODO + old_decomp, new_decomp = None, old_decomp source_terminator.replace_with(ir.Delay(iodelay.Const(target_time_delta), {}, new_decomp, source_terminator.target())) diff --git a/lit-test/test/interleaving/indirect.py b/lit-test/test/interleaving/indirect.py new file mode 100644 index 000000000..19f59c69f --- /dev/null +++ b/lit-test/test/interleaving/indirect.py @@ -0,0 +1,28 @@ +# RUN: %python -m artiq.compiler.testbench.jit %s >%t +# RUN: OutputCheck %s --file-to-check=%t + +def f(): + delay_mu(2) + +def g(): + with parallel: + 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/lit-test/test/interleaving/indirect_arg.py b/lit-test/test/interleaving/indirect_arg.py new file mode 100644 index 000000000..e9af00034 --- /dev/null +++ b/lit-test/test/interleaving/indirect_arg.py @@ -0,0 +1,28 @@ +# 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 parallel: + 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() From 5cd12ffd28abc66c185f2d1e36f0c8eb7e4b2b44 Mon Sep 17 00:00:00 2001 From: whitequark Date: Sat, 21 Nov 2015 17:23:20 +0800 Subject: [PATCH 345/369] compiler.iodelay: fold MUToS and SToMU. --- artiq/compiler/iodelay.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/artiq/compiler/iodelay.py b/artiq/compiler/iodelay.py index 47e04472a..099f0953a 100644 --- a/artiq/compiler/iodelay.py +++ b/artiq/compiler/iodelay.py @@ -106,6 +106,12 @@ class MUToS(Conv): def eval(self, env): return self.operand.eval(env) * self.ref_period + def fold(self, vars=None): + if isinstance(self.operand, Const): + return Const(self.operand.value * self.ref_period) + else: + return super().fold(vars) + class SToMU(Conv): def __str__(self): return "s->mu({})".format(self.operand) @@ -113,6 +119,12 @@ class SToMU(Conv): def eval(self, env): return self.operand.eval(env) / self.ref_period + def fold(self, vars=None): + if isinstance(self.operand, Const): + return Const(self.operand.value / self.ref_period) + else: + return super().fold(vars) + class BinOp(Expr): def __init__(self, lhs, rhs): self.lhs, self.rhs = lhs, rhs From a01e328b4a0c64f66766a845751cfcf62093a887 Mon Sep 17 00:00:00 2001 From: whitequark Date: Sat, 21 Nov 2015 17:24:00 +0800 Subject: [PATCH 346/369] transforms.interleaver: don't assume all delay expressions are folded. --- artiq/compiler/transforms/interleaver.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/artiq/compiler/transforms/interleaver.py b/artiq/compiler/transforms/interleaver.py index 1fc8b4e1e..e1348ecd6 100644 --- a/artiq/compiler/transforms/interleaver.py +++ b/artiq/compiler/transforms/interleaver.py @@ -65,8 +65,9 @@ class Interleaver: terminator = block.terminator() if isinstance(terminator, ir.Delay): # We should be able to fold everything without free variables. - assert iodelay.is_const(terminator.expr) - return terminator.expr.value + folded_expr = terminator.expr.fold() + assert iodelay.is_const(folded_expr) + return folded_expr.value else: return 0 From af43c661490c43cca9dd59d09e2b464cfd13c1a8 Mon Sep 17 00:00:00 2001 From: whitequark Date: Sat, 21 Nov 2015 17:37:14 +0800 Subject: [PATCH 347/369] artiq_compile: set file_import prefix, like in artiq_run. --- artiq/frontend/artiq_compile.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/artiq/frontend/artiq_compile.py b/artiq/frontend/artiq_compile.py index b331c5a79..50c7d82af 100755 --- a/artiq/frontend/artiq_compile.py +++ b/artiq/frontend/artiq_compile.py @@ -40,7 +40,7 @@ def main(): dataset_mgr = DatasetManager(DatasetDB(args.dataset_db)) try: - module = file_import(args.file) + module = file_import(args.file, prefix="artiq_run_") exp = get_experiment(module, args.experiment) arguments = parse_arguments(args.arguments) exp_inst = exp(device_mgr, dataset_mgr, **arguments) From 73845279ae525d09206db89db2cf7d013b5a3931 Mon Sep 17 00:00:00 2001 From: whitequark Date: Mon, 23 Nov 2015 18:08:33 +0800 Subject: [PATCH 348/369] transforms.interleaver: determine when inlining is not necessary. --- artiq/compiler/transforms/interleaver.py | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/artiq/compiler/transforms/interleaver.py b/artiq/compiler/transforms/interleaver.py index e1348ecd6..5f33bcf2d 100644 --- a/artiq/compiler/transforms/interleaver.py +++ b/artiq/compiler/transforms/interleaver.py @@ -25,6 +25,9 @@ def delay_free_subgraph(root, limit): return True +def is_pure_delay(insn): + return isinstance(insn, ir.Builtin) and insn.op in ("delay", "delay_mu") + class Interleaver: def __init__(self, engine): self.engine = engine @@ -98,14 +101,26 @@ class Interleaver: if target_time_delta > 0: assert isinstance(source_terminator, ir.Delay) - if isinstance(old_decomp, ir.Builtin) and \ - old_decomp.op in ("delay", "delay_mu"): + if is_pure_delay(old_decomp): new_decomp_expr = ir.Constant(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(source_terminator, new_decomp) - else: - old_decomp, new_decomp = None, old_decomp + else: # It's a call. + need_to_inline = False + for other_source_block in filter(lambda block: block != source_block, + source_blocks): + other_source_terminator = other_source_block.terminator() + if not (is_pure_delay(other_source_terminator.decomposition()) and \ + iodelay.is_const(other_source_terminator.expr) and \ + other_source_terminator.expr.fold().value >= source_block_delay): + need_to_inline = True + break + + if need_to_inline: + assert False + else: + old_decomp, new_decomp = None, old_decomp source_terminator.replace_with(ir.Delay(iodelay.Const(target_time_delta), {}, new_decomp, source_terminator.target())) From 9fc7a4203600b8f5890afcfdbcda8d8e0cfc82ab Mon Sep 17 00:00:00 2001 From: whitequark Date: Mon, 23 Nov 2015 18:26:45 +0800 Subject: [PATCH 349/369] pipistrello: expose LED{1..4} as RTIO channels. --- artiq/gateware/targets/pipistrello.py | 14 ++++------- doc/manual/core_device.rst | 36 ++++++++++++++++----------- 2 files changed, 26 insertions(+), 24 deletions(-) diff --git a/artiq/gateware/targets/pipistrello.py b/artiq/gateware/targets/pipistrello.py index e0696ef9d..8abeb46ef 100755 --- a/artiq/gateware/targets/pipistrello.py +++ b/artiq/gateware/targets/pipistrello.py @@ -128,12 +128,7 @@ trce -v 12 -fastpaths -tsi {build_name}.tsi -o {build_name}.twr {build_name}.ncd """ platform.add_extension(nist_qc1.papilio_adapter_io) - self.submodules.leds = gpio.GPIOOut(Cat( - platform.request("user_led", 0), - platform.request("user_led", 1), - platform.request("user_led", 2), - platform.request("user_led", 3), - )) + self.submodules.leds = gpio.GPIOOut(platform.request("user_led", 4)) self.comb += [ platform.request("ttl_l_tx_en").eq(1), @@ -170,9 +165,10 @@ trce -v 12 -fastpaths -tsi {build_name}.tsi -o {build_name}.twr {build_name}.ncd self.submodules += phy rtio_channels.append(rtio.Channel.from_phy(phy, ofifo_depth=4)) - phy = ttl_simple.Output(platform.request("user_led", 4)) - self.submodules += phy - rtio_channels.append(rtio.Channel.from_phy(phy, ofifo_depth=4)) + for led_number in range(4): + phy = ttl_simple.Output(platform.request("user_led", led_number)) + self.submodules += phy + rtio_channels.append(rtio.Channel.from_phy(phy, ofifo_depth=4)) self.add_constant("RTIO_REGULAR_TTL_COUNT", len(rtio_channels)) diff --git a/doc/manual/core_device.rst b/doc/manual/core_device.rst index d01a992a6..942a4b844 100644 --- a/doc/manual/core_device.rst +++ b/doc/manual/core_device.rst @@ -54,21 +54,27 @@ The low-cost Pipistrello FPGA board can be used as a lower-cost but slower alter When plugged to an adapter, the NIST QC1 hardware can be used. The TTL lines are mapped to RTIO channels as follows: -+--------------+----------+------------+ -| RTIO channel | TTL line | Capability | -+==============+==========+============+ -| 0 | PMT0 | Input | -+--------------+----------+------------+ -| 1 | PMT1 | Input | -+--------------+----------+------------+ -| 2-16 | TTL0-14 | Output | -+--------------+----------+------------+ -| 17 | EXT_LED | Output | -+--------------+----------+------------+ -| 18 | USER_LED | Output | -+--------------+----------+------------+ -| 19 | TTL15 | Clock | -+--------------+----------+------------+ ++--------------+------------+------------+ +| RTIO channel | TTL line | Capability | ++==============+============+============+ +| 0 | PMT0 | Input | ++--------------+------------+------------+ +| 1 | PMT1 | Input | ++--------------+------------+------------+ +| 2-16 | TTL0-14 | Output | ++--------------+------------+------------+ +| 17 | EXT_LED | Output | ++--------------+------------+------------+ +| 18 | USER_LED_1 | Output | ++--------------+------------+------------+ +| 19 | USER_LED_2 | Output | ++--------------+------------+------------+ +| 20 | USER_LED_3 | Output | ++--------------+------------+------------+ +| 21 | USER_LED_4 | Output | ++--------------+------------+------------+ +| 22 | TTL15 | Clock | ++--------------+------------+------------+ The input only limitation on channels 0 and 1 comes from the QC-DAQ adapter. When the adapter is not used (and physically unplugged from the Pipistrello board), the corresponding pins on the Pipistrello can be used as outputs. Do not configure these channels as outputs when the adapter is plugged, as this would cause electrical contention. From 03b4e4027c72aedd9d811a539c8c2e927f23f89f Mon Sep 17 00:00:00 2001 From: whitequark Date: Mon, 23 Nov 2015 18:53:42 +0800 Subject: [PATCH 350/369] transforms.interleaver: fix IR type/value mismatch. --- artiq/compiler/transforms/interleaver.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/artiq/compiler/transforms/interleaver.py b/artiq/compiler/transforms/interleaver.py index 5f33bcf2d..282c84534 100644 --- a/artiq/compiler/transforms/interleaver.py +++ b/artiq/compiler/transforms/interleaver.py @@ -102,7 +102,7 @@ class Interleaver: assert isinstance(source_terminator, ir.Delay) if is_pure_delay(old_decomp): - new_decomp_expr = ir.Constant(target_time_delta, builtins.TInt64()) + 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(source_terminator, new_decomp) From 0bf425eefa601ef06f4bbc86bfe0436feab45d6d Mon Sep 17 00:00:00 2001 From: whitequark Date: Mon, 23 Nov 2015 19:18:58 +0800 Subject: [PATCH 351/369] compiler.ir: maintain use lists while mutating instructions. --- artiq/compiler/ir.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/artiq/compiler/ir.py b/artiq/compiler/ir.py index 4d29e1529..e7756e73b 100644 --- a/artiq/compiler/ir.py +++ b/artiq/compiler/ir.py @@ -964,7 +964,9 @@ class Branch(Terminator): 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): """ @@ -1231,11 +1233,18 @@ class Delay(Terminator): 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 substs(self): return zip(self.var_names, self.operands[2:]) From c73b2c1a78c0ef0c9dd57d0446a6b1c8016f6ece Mon Sep 17 00:00:00 2001 From: whitequark Date: Mon, 23 Nov 2015 21:21:01 +0800 Subject: [PATCH 352/369] compiler.ir: fix typo. --- artiq/compiler/ir.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/artiq/compiler/ir.py b/artiq/compiler/ir.py index e7756e73b..1c41ca5c1 100644 --- a/artiq/compiler/ir.py +++ b/artiq/compiler/ir.py @@ -232,10 +232,10 @@ class Phi(Instruction): yield next(operand_iter), next(operand_iter) def incoming_blocks(self): - (block for (block, value) in self.incoming()) + return (block for (block, value) in self.incoming()) def incoming_values(self): - (value for (block, value) in self.incoming()) + return (value for (block, value) in self.incoming()) def incoming_value_for_block(self, target_block): for (block, value) in self.incoming(): From d92b3434a0eae214ecd06c66cab56b4da9522666 Mon Sep 17 00:00:00 2001 From: whitequark Date: Mon, 23 Nov 2015 21:44:38 +0800 Subject: [PATCH 353/369] compiler.ir: print basic blocks in reverse postorder for readability. --- artiq/compiler/ir.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/artiq/compiler/ir.py b/artiq/compiler/ir.py index 1c41ca5c1..055d0f2ed 100644 --- a/artiq/compiler/ir.py +++ b/artiq/compiler/ir.py @@ -463,12 +463,23 @@ class Function: 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))) - for block in self.basic_blocks: + for block in reversed(postorder): lines.append(block.as_entity(type_printer)) lines.append("}") return "\n".join(lines) From a4525b21cf4e02417ba4b63eeea922e2334505a2 Mon Sep 17 00:00:00 2001 From: whitequark Date: Mon, 23 Nov 2015 23:55:12 +0800 Subject: [PATCH 354/369] compiler.ir: print even blocks without predecessors. --- artiq/compiler/ir.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/artiq/compiler/ir.py b/artiq/compiler/ir.py index 055d0f2ed..6a363ef43 100644 --- a/artiq/compiler/ir.py +++ b/artiq/compiler/ir.py @@ -479,8 +479,12 @@ class Function: type_printer.name(self.type.ret), self.name, ", ".join([arg.as_operand(type_printer) for arg in self.arguments]), type_printer.name(self.type))) - for block in reversed(postorder): + + 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) From f0fd6cd0cab2123c092eba5c3c9f11d045896872 Mon Sep 17 00:00:00 2001 From: whitequark Date: Mon, 23 Nov 2015 23:58:37 +0800 Subject: [PATCH 355/369] compiler.algorithms.inline: implement. --- artiq/compiler/algorithms/__init__.py | 1 + artiq/compiler/algorithms/inline.py | 80 +++++++++++++++++++++++++++ artiq/compiler/ir.py | 77 ++++++++++++++++++++++++++ 3 files changed, 158 insertions(+) create mode 100644 artiq/compiler/algorithms/__init__.py create mode 100644 artiq/compiler/algorithms/inline.py diff --git a/artiq/compiler/algorithms/__init__.py b/artiq/compiler/algorithms/__init__.py new file mode 100644 index 000000000..47fcd2dbf --- /dev/null +++ b/artiq/compiler/algorithms/__init__.py @@ -0,0 +1 @@ +from .inline import inline diff --git a/artiq/compiler/algorithms/inline.py b/artiq/compiler/algorithms/inline.py new file mode 100644 index 000000000..5030f23fa --- /dev/null +++ b/artiq/compiler/algorithms/inline.py @@ -0,0 +1,80 @@ +""" +: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(call_insn, + ir.GetAttr(call_insn.target_function(), '__closure__')) + 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): + substs = source_insn.substs() + mapped_substs = {var: value_map[substs[var]] for var in substs} + const_substs = {var: iodelay.Const(mapped_substs[var].value) + for var in mapped_substs + if isinstance(mapped_substs[var], ir.Constant)} + other_substs = {var: mapped_substs[var] + for var in mapped_substs + if not isinstance(mapped_substs[var], ir.Constant)} + target_insn = ir.Delay(source_insn.expr.fold(const_substs), other_substs, + value_map[source_insn.decomposition()], + value_map[source_insn.target()]) + 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/ir.py b/artiq/compiler/ir.py index 6a363ef43..1586c5bd4 100644 --- a/artiq/compiler/ir.py +++ b/artiq/compiler/ir.py @@ -163,6 +163,12 @@ class Instruction(User): 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) + return self_copy + def set_basic_block(self, new_basic_block): self.basic_block = new_basic_block if self.basic_block is not None: @@ -585,6 +591,11 @@ class GetLocal(Instruction): 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)) @@ -613,6 +624,11 @@ class SetLocal(Instruction): 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)) @@ -643,6 +659,11 @@ class GetConstructor(Instruction): super().__init__([env], var_type, 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 "getconstructor({})".format(repr(self.var_name)) @@ -672,6 +693,11 @@ class GetAttr(Instruction): 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)) @@ -701,6 +727,11 @@ class SetAttr(Instruction): 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)) @@ -798,6 +829,11 @@ class Arith(Instruction): 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__) @@ -827,6 +863,11 @@ class Compare(Instruction): 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__) @@ -853,6 +894,11 @@ class Builtin(Instruction): 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) @@ -874,6 +920,11 @@ class Closure(Instruction): 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) @@ -898,6 +949,11 @@ class Call(Instruction): super().__init__([func] + args, func.type.ret, name) self.static_target_function = None + def copy(self, mapper): + self_copy = super().copy(mapper) + self_copy.static_target_function = self.static_target_function + return self_copy + def opcode(self): return "call" @@ -957,6 +1013,11 @@ class Quote(Instruction): 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)) @@ -1148,6 +1209,11 @@ class Invoke(Terminator): super().__init__([func] + args + [normal, exn], func.type.ret, name) self.static_target_function = None + def copy(self, mapper): + self_copy = super().copy(mapper) + self_copy.static_target_function = self.static_target_function + return self_copy + def opcode(self): return "invoke" @@ -1191,6 +1257,11 @@ class LandingPad(Terminator): 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" @@ -1245,6 +1316,12 @@ class Delay(Terminator): self.expr = expr self.var_names = list(substs.keys()) + def copy(self, mapper): + self_copy = super().copy(mapper) + self_copy.expr = self.expr + self_copy.var_names = list(self.var_names) + return self_copy + def decomposition(self): return self.operands[0] From f3da227e2d7cee93c26ec7104a9507f11ef4908e Mon Sep 17 00:00:00 2001 From: whitequark Date: Mon, 23 Nov 2015 23:59:25 +0800 Subject: [PATCH 356/369] compiler.ir: change argument order for BasicBlock.insert. --- artiq/compiler/algorithms/inline.py | 4 ++-- artiq/compiler/ir.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/artiq/compiler/algorithms/inline.py b/artiq/compiler/algorithms/inline.py index 5030f23fa..b0b691919 100644 --- a/artiq/compiler/algorithms/inline.py +++ b/artiq/compiler/algorithms/inline.py @@ -25,8 +25,8 @@ def inline(call_insn): else: target_return_phi = target_successor.prepend(ir.Phi(source_function.type.ret)) - closure = target_predecessor.insert(call_insn, - ir.GetAttr(call_insn.target_function(), '__closure__')) + 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 diff --git a/artiq/compiler/ir.py b/artiq/compiler/ir.py index 1586c5bd4..513849268 100644 --- a/artiq/compiler/ir.py +++ b/artiq/compiler/ir.py @@ -338,7 +338,7 @@ class BasicBlock(NamedValue): def index(self, insn): return self.instructions.index(insn) - def insert(self, before, insn): + def insert(self, insn, before): assert isinstance(insn, Instruction) insn.set_basic_block(self) self.instructions.insert(self.index(before), insn) @@ -351,7 +351,7 @@ class BasicBlock(NamedValue): return insn def replace(self, insn, replacement): - self.insert(insn, replacement) + self.insert(replacement, before=insn) self.remove(insn) def is_terminated(self): From 02f2763ea849cb59ef18edaf28b61ecf89ac65d4 Mon Sep 17 00:00:00 2001 From: whitequark Date: Mon, 23 Nov 2015 23:59:59 +0800 Subject: [PATCH 357/369] compiler.iodelay: always fully fold SToMU and MUToS. --- artiq/compiler/iodelay.py | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/artiq/compiler/iodelay.py b/artiq/compiler/iodelay.py index 099f0953a..bb5b5186a 100644 --- a/artiq/compiler/iodelay.py +++ b/artiq/compiler/iodelay.py @@ -95,10 +95,6 @@ class Conv(Expr): def free_vars(self): return self.operand.free_vars() - def fold(self, vars=None): - return self.__class__(self.operand.fold(vars), - ref_period=self.ref_period) - class MUToS(Conv): def __str__(self): return "mu->s({})".format(self.operand) @@ -107,10 +103,11 @@ class MUToS(Conv): return self.operand.eval(env) * self.ref_period def fold(self, vars=None): - if isinstance(self.operand, Const): - return Const(self.operand.value * self.ref_period) + operand = self.operand.fold(vars) + if isinstance(operand, Const): + return Const(operand.value * self.ref_period) else: - return super().fold(vars) + return MUToS(operand, ref_period=self.ref_period) class SToMU(Conv): def __str__(self): @@ -120,10 +117,11 @@ class SToMU(Conv): return self.operand.eval(env) / self.ref_period def fold(self, vars=None): - if isinstance(self.operand, Const): - return Const(self.operand.value / self.ref_period) + operand = self.operand.fold(vars) + if isinstance(operand, Const): + return Const(operand.value / self.ref_period) else: - return super().fold(vars) + return SToMU(operand, ref_period=self.ref_period) class BinOp(Expr): def __init__(self, lhs, rhs): From 2a82eb72191ffac3d7d5cfb87466804d0db9ca16 Mon Sep 17 00:00:00 2001 From: whitequark Date: Tue, 24 Nov 2015 00:01:10 +0800 Subject: [PATCH 358/369] compiler.ir: return dict from Delay.substs, not pair iterable. --- artiq/compiler/ir.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/artiq/compiler/ir.py b/artiq/compiler/ir.py index 513849268..54c6f0500 100644 --- a/artiq/compiler/ir.py +++ b/artiq/compiler/ir.py @@ -1339,13 +1339,14 @@ class Delay(Terminator): self.operands[1].uses.add(self) def substs(self): - return zip(self.var_names, self.operands[2:]) + return {key: value for key, value in zip(self.var_names, self.operands[2:])} def _operands_as_string(self, type_printer): - substs = [] - for var_name, operand in self.substs(): - substs.append("{}={}".format(var_name, operand)) - result = "[{}]".format(", ".join(substs)) + substs = self.substs() + substs_as_strings = [] + for var_name in substs: + substs_as_strings.append("{} = {}".format(var_name, substs[var_name])) + result = "[{}]".format(", ".join(substs_as_strings)) result += ", decomp {}, to {}".format(self.decomposition().as_operand(type_printer), self.target().as_operand(type_printer)) return result From 178ff74da2bf249b43185197688c42ff228b9511 Mon Sep 17 00:00:00 2001 From: whitequark Date: Tue, 24 Nov 2015 00:02:07 +0800 Subject: [PATCH 359/369] transforms.interleaver: inline calls. --- artiq/compiler/transforms/interleaver.py | 95 +++++++++++++----------- 1 file changed, 50 insertions(+), 45 deletions(-) diff --git a/artiq/compiler/transforms/interleaver.py b/artiq/compiler/transforms/interleaver.py index 282c84534..873e55eac 100644 --- a/artiq/compiler/transforms/interleaver.py +++ b/artiq/compiler/transforms/interleaver.py @@ -5,6 +5,7 @@ the timestamp would always monotonically nondecrease. from .. import types, builtins, ir, iodelay from ..analyses import domination +from ..algorithms import inline def delay_free_subgraph(root, limit): visited = set() @@ -25,9 +26,24 @@ def delay_free_subgraph(root, limit): 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.expr.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 @@ -64,25 +80,24 @@ class Interleaver: source_times = [0 for _ in source_blocks] while len(source_blocks) > 0: - 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.expr.fold() - assert iodelay.is_const(folded_expr) - return folded_expr.value - else: - return 0 - def time_after_block(pair): index, block = pair return source_times[index] + iodelay_of_block(block) - index, source_block = min(enumerate(source_blocks), key=time_after_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.Parallel): @@ -93,42 +108,32 @@ class Interleaver: source_terminator = source_block.terminator() - if isinstance(source_terminator, ir.Delay): - old_decomp = source_terminator.decomposition() - else: - old_decomp = None - - if target_time_delta > 0: - assert isinstance(source_terminator, ir.Delay) - - if is_pure_delay(old_decomp): - 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(source_terminator, new_decomp) - else: # It's a call. - need_to_inline = False - for other_source_block in filter(lambda block: block != source_block, - source_blocks): - other_source_terminator = other_source_block.terminator() - if not (is_pure_delay(other_source_terminator.decomposition()) and \ - iodelay.is_const(other_source_terminator.expr) and \ - other_source_terminator.expr.fold().value >= source_block_delay): - need_to_inline = True - break - - if need_to_inline: - assert False - else: - old_decomp, new_decomp = None, old_decomp - - source_terminator.replace_with(ir.Delay(iodelay.Const(target_time_delta), {}, - new_decomp, source_terminator.target())) - else: + if not isinstance(source_terminator, ir.Delay): source_terminator.replace_with(ir.Branch(source_terminator.target())) + else: + 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 - if old_decomp is not None: - old_decomp.erase() + source_terminator.basic_block.insert(new_decomp, before=source_terminator) + source_terminator.expr = 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: + inline(old_decomp) + postdom_tree = domination.PostDominatorTree(func) + continue + elif target_time_delta > 0: + source_terminator.expr = iodelay.Const(target_time_delta) + else: + source_terminator.replace_with(ir.Branch(source_terminator.target())) target_block = source_block target_time = new_target_time From abb36b42be2ab84191d0bd2e475f8c65cf7a4096 Mon Sep 17 00:00:00 2001 From: whitequark Date: Tue, 24 Nov 2015 00:19:33 +0800 Subject: [PATCH 360/369] compiler.iodelay: fold and eval SToMU to an int, not float. --- artiq/compiler/iodelay.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/artiq/compiler/iodelay.py b/artiq/compiler/iodelay.py index bb5b5186a..bdf96b46a 100644 --- a/artiq/compiler/iodelay.py +++ b/artiq/compiler/iodelay.py @@ -114,12 +114,12 @@ class SToMU(Conv): return "s->mu({})".format(self.operand) def eval(self, env): - return self.operand.eval(env) / self.ref_period + 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(operand.value / self.ref_period) + return Const(int(operand.value / self.ref_period)) else: return SToMU(operand, ref_period=self.ref_period) From 37b80247f15780be9aa00bfeb4a2a0b8a3882720 Mon Sep 17 00:00:00 2001 From: whitequark Date: Tue, 24 Nov 2015 00:20:00 +0800 Subject: [PATCH 361/369] lit-test: fix breakage from abb36b4 and 02f2763. --- lit-test/test/iodelay/call.py | 4 ++-- lit-test/test/iodelay/class.py | 2 +- lit-test/test/iodelay/linear.py | 8 ++++---- lit-test/test/iodelay/loop.py | 8 ++++---- lit-test/test/iodelay/order_invariance.py | 4 ++-- lit-test/test/iodelay/range.py | 12 ++++++------ lit-test/test/iodelay/return.py | 4 ++-- 7 files changed, 21 insertions(+), 21 deletions(-) diff --git a/lit-test/test/iodelay/call.py b/lit-test/test/iodelay/call.py index 65580812a..e08446da1 100644 --- a/lit-test/test/iodelay/call.py +++ b/lit-test/test/iodelay/call.py @@ -2,9 +2,9 @@ # RUN: OutputCheck %s --file-to-check=%t def f(): - delay(1.0) + delay_mu(1) -# CHECK-L: g: ()->NoneType delay(s->mu(2.0) mu) +# CHECK-L: g: ()->NoneType delay(2 mu) def g(): f() f() diff --git a/lit-test/test/iodelay/class.py b/lit-test/test/iodelay/class.py index 61ca122f8..6bcb9339b 100644 --- a/lit-test/test/iodelay/class.py +++ b/lit-test/test/iodelay/class.py @@ -1,7 +1,7 @@ # RUN: %python -m artiq.compiler.testbench.signature %s >%t # RUN: OutputCheck %s --file-to-check=%t -# CHECK-L: g: (i:)->NoneType delay(s->mu(1.0) mu) +# CHECK-L: g: (i:)->NoneType delay(1000000 mu) def g(i): i.f(1.0) diff --git a/lit-test/test/iodelay/linear.py b/lit-test/test/iodelay/linear.py index 7037516f6..cb2bed204 100644 --- a/lit-test/test/iodelay/linear.py +++ b/lit-test/test/iodelay/linear.py @@ -1,12 +1,12 @@ # RUN: %python -m artiq.compiler.testbench.signature %s >%t # RUN: OutputCheck %s --file-to-check=%t -# CHECK-L: f: ()->NoneType delay(s->mu(1.0) + 1000 mu) +# CHECK-L: f: ()->NoneType delay(1001000 mu) def f(): delay(1.0) delay_mu(1000) -# CHECK-L: g: ()->NoneType delay(s->mu(5.0) mu) +# CHECK-L: g: ()->NoneType delay(3 mu) def g(): - delay(1.0) - delay(2.0 * 2) + delay_mu(1) + delay_mu(2) diff --git a/lit-test/test/iodelay/loop.py b/lit-test/test/iodelay/loop.py index 228756224..37bcb9e07 100644 --- a/lit-test/test/iodelay/loop.py +++ b/lit-test/test/iodelay/loop.py @@ -1,13 +1,13 @@ # RUN: %python -m artiq.compiler.testbench.signature %s >%t # RUN: OutputCheck %s --file-to-check=%t -# CHECK-L: f: ()->NoneType delay(s->mu(1.5) * 10 mu) +# CHECK-L: f: ()->NoneType delay(30 mu) def f(): for _ in range(10): - delay(1.5) + delay_mu(3) -# CHECK-L: g: ()->NoneType delay(s->mu(1.5) * 2 * 10 mu) +# CHECK-L: g: ()->NoneType delay(60 mu) def g(): for _ in range(10): for _ in range(2): - delay(1.5) + delay_mu(3) diff --git a/lit-test/test/iodelay/order_invariance.py b/lit-test/test/iodelay/order_invariance.py index af5adf13c..e4deb96c5 100644 --- a/lit-test/test/iodelay/order_invariance.py +++ b/lit-test/test/iodelay/order_invariance.py @@ -1,10 +1,10 @@ # RUN: %python -m artiq.compiler.testbench.signature %s >%t # RUN: OutputCheck %s --file-to-check=%t -# CHECK-L: g: ()->NoneType delay(s->mu(2.0) mu) +# CHECK-L: g: ()->NoneType delay(2 mu) def g(): f() f() def f(): - delay(1.0) + delay_mu(1) diff --git a/lit-test/test/iodelay/range.py b/lit-test/test/iodelay/range.py index 498ee6eb5..9c4a73a79 100644 --- a/lit-test/test/iodelay/range.py +++ b/lit-test/test/iodelay/range.py @@ -1,20 +1,20 @@ # RUN: %python -m artiq.compiler.testbench.signature %s >%t # RUN: OutputCheck %s --file-to-check=%t -# CHECK-L: f: (a:int(width=32))->NoneType delay(s->mu(1.5) * a mu) +# CHECK-L: f: (a:int(width=32))->NoneType delay(3 * a mu) def f(a): for _ in range(a): - delay(1.5) + delay_mu(3) -# CHECK-L: g: (a:int(width=32), b:int(width=32))->NoneType delay(s->mu(1.5) * (b - a) mu) +# CHECK-L: g: (a:int(width=32), b:int(width=32))->NoneType delay(3 * (b - a) mu) def g(a, b): for _ in range(a, b): - delay(1.5) + delay_mu(3) -# CHECK-L: h: (a:int(width=32), b:int(width=32), c:int(width=32))->NoneType delay(s->mu(1.5) * (b - a) // c mu) +# CHECK-L: h: (a:int(width=32), b:int(width=32), c:int(width=32))->NoneType delay(3 * (b - a) // c mu) def h(a, b, c): for _ in range(a, b, c): - delay(1.5) + delay_mu(3) f(1) g(1,2) diff --git a/lit-test/test/iodelay/return.py b/lit-test/test/iodelay/return.py index 64398dfc4..d07267a44 100644 --- a/lit-test/test/iodelay/return.py +++ b/lit-test/test/iodelay/return.py @@ -1,10 +1,10 @@ # RUN: %python -m artiq.compiler.testbench.signature %s >%t # RUN: OutputCheck %s --file-to-check=%t -# CHECK-L: f: ()->int(width=32) delay(s->mu(1.5) * 10 mu) +# CHECK-L: f: ()->int(width=32) delay(30 mu) def f(): for _ in range(10): - delay(1.5) + delay_mu(3) return 10 # CHECK-L: g: (x:float)->int(width=32) From 32fe4a8a0c5d53b2fac4d34151842fa1818c9e26 Mon Sep 17 00:00:00 2001 From: whitequark Date: Tue, 24 Nov 2015 00:20:33 +0800 Subject: [PATCH 362/369] transforms.llvm_ir_generator: don't assert on inlined functions. --- artiq/compiler/transforms/llvm_ir_generator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/artiq/compiler/transforms/llvm_ir_generator.py b/artiq/compiler/transforms/llvm_ir_generator.py index 0df479acc..ac54ecf9f 100644 --- a/artiq/compiler/transforms/llvm_ir_generator.py +++ b/artiq/compiler/transforms/llvm_ir_generator.py @@ -541,7 +541,7 @@ class LLVMIRGenerator: # 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 isinstance(insn.value(), ir.EnvironmentArgument) + assert insn.var_name == '$outer' llvalue = self.llbuilder.bitcast(llvalue, llptr.type.pointee) return self.llbuilder.store(llvalue, llptr) From e53f26dba0024f8c407a8ca11a3c71cf88e1dac0 Mon Sep 17 00:00:00 2001 From: whitequark Date: Tue, 24 Nov 2015 00:21:53 +0800 Subject: [PATCH 363/369] lit-test: add interleaving/pure_impure_tie. --- lit-test/test/interleaving/pure_impure_tie.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 lit-test/test/interleaving/pure_impure_tie.py diff --git a/lit-test/test/interleaving/pure_impure_tie.py b/lit-test/test/interleaving/pure_impure_tie.py new file mode 100644 index 000000000..f2eab0502 --- /dev/null +++ b/lit-test/test/interleaving/pure_impure_tie.py @@ -0,0 +1,14 @@ +# RUN: %python -m artiq.compiler.testbench.jit %s >%t +# RUN: OutputCheck %s --file-to-check=%t + +def f(): + delay_mu(2) + +def g(): + with parallel: + f() + delay_mu(2) + print(now_mu()) + +# CHECK-L: 2 +g() From 9bc62fa3d2de9964b1109c538f083aea93f2c51d Mon Sep 17 00:00:00 2001 From: whitequark Date: Tue, 24 Nov 2015 00:46:26 +0800 Subject: [PATCH 364/369] transforms.iodelay_estimator: correctly handle functions with empty body. --- artiq/compiler/iodelay.py | 1 + artiq/compiler/transforms/iodelay_estimator.py | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/artiq/compiler/iodelay.py b/artiq/compiler/iodelay.py index bdf96b46a..3a3283199 100644 --- a/artiq/compiler/iodelay.py +++ b/artiq/compiler/iodelay.py @@ -208,6 +208,7 @@ class Max(Expr): 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): diff --git a/artiq/compiler/transforms/iodelay_estimator.py b/artiq/compiler/transforms/iodelay_estimator.py index 21d2d9616..4170094c0 100644 --- a/artiq/compiler/transforms/iodelay_estimator.py +++ b/artiq/compiler/transforms/iodelay_estimator.py @@ -220,7 +220,8 @@ class IODelayEstimator(algorithm.Visitor): delays.append(self.current_delay) self.current_delay = old_delay - self.current_delay += iodelay.Max(delays) + 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 From d3f0059cab5f08e727047def5800acd0c466a38c Mon Sep 17 00:00:00 2001 From: whitequark Date: Tue, 24 Nov 2015 00:46:55 +0800 Subject: [PATCH 365/369] compiler.iodelay: correctly fold max(0, [0, ]...). --- artiq/compiler/iodelay.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/artiq/compiler/iodelay.py b/artiq/compiler/iodelay.py index 3a3283199..6cab8588c 100644 --- a/artiq/compiler/iodelay.py +++ b/artiq/compiler/iodelay.py @@ -231,7 +231,7 @@ class Max(Expr): consts.append(operand.value) elif operand not in exprs: exprs.append(operand) - if any(consts): + if len(consts) > 0: exprs.append(Const(max(consts))) if len(exprs) == 1: return exprs[0] From 2bfc72fba947ece194d21b05b083c263dd3a3bad Mon Sep 17 00:00:00 2001 From: whitequark Date: Tue, 24 Nov 2015 00:54:20 +0800 Subject: [PATCH 366/369] testbench.embedding: fix ref_period mismatch. --- artiq/compiler/testbench/embedding.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/artiq/compiler/testbench/embedding.py b/artiq/compiler/testbench/embedding.py index c20693560..52932ff93 100644 --- a/artiq/compiler/testbench/embedding.py +++ b/artiq/compiler/testbench/embedding.py @@ -20,7 +20,7 @@ def main(): ddb_path = os.path.join(os.path.dirname(sys.argv[1]), "device_db.pyon") try: - core = Core(dmgr=DeviceManager(DeviceDB(ddb_path))) + core = DeviceManager(DeviceDB(ddb_path)).get("core") if compile_only: core.compile(testcase_vars["entrypoint"], (), {}) else: From 8527e306c3bb87dc720684abd95169e0d8b8553b Mon Sep 17 00:00:00 2001 From: whitequark Date: Tue, 24 Nov 2015 02:02:34 +0800 Subject: [PATCH 367/369] testbench.embedding: use dmgr to get core and export it. --- artiq/compiler/testbench/embedding.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/artiq/compiler/testbench/embedding.py b/artiq/compiler/testbench/embedding.py index 52932ff93..abe5f2fd3 100644 --- a/artiq/compiler/testbench/embedding.py +++ b/artiq/compiler/testbench/embedding.py @@ -12,15 +12,16 @@ def main(): else: compile_only = False + ddb_path = os.path.join(os.path.dirname(sys.argv[1]), "device_db.pyon") + dmgr = DeviceManager(DeviceDB(ddb_path)) + with open(sys.argv[1]) as f: testcase_code = compile(f.read(), f.name, "exec") - testcase_vars = {'__name__': 'testbench'} + testcase_vars = {'__name__': 'testbench', 'dmgr': dmgr} exec(testcase_code, testcase_vars) - ddb_path = os.path.join(os.path.dirname(sys.argv[1]), "device_db.pyon") - try: - core = DeviceManager(DeviceDB(ddb_path)).get("core") + core = dmgr.get("core") if compile_only: core.compile(testcase_vars["entrypoint"], (), {}) else: From fec5c2ebf00b7bbf2619523ff45fe077265eb608 Mon Sep 17 00:00:00 2001 From: whitequark Date: Tue, 24 Nov 2015 02:52:41 +0800 Subject: [PATCH 368/369] transforms.interleaver: add a diagnostic for interleave inlining failure. --- artiq/compiler/transforms/interleaver.py | 10 ++++++++++ lit-test/test/interleaving/error_inlining.py | 16 ++++++++++++++++ 2 files changed, 26 insertions(+) create mode 100644 lit-test/test/interleaving/error_inlining.py diff --git a/artiq/compiler/transforms/interleaver.py b/artiq/compiler/transforms/interleaver.py index 873e55eac..4aa3c327b 100644 --- a/artiq/compiler/transforms/interleaver.py +++ b/artiq/compiler/transforms/interleaver.py @@ -3,6 +3,8 @@ the timestamp would always monotonically nondecrease. """ +from pythonparser import diagnostic + from .. import types, builtins, ir, iodelay from ..analyses import domination from ..algorithms import inline @@ -127,6 +129,14 @@ class Interleaver: 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 parallel:' 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 diff --git a/lit-test/test/interleaving/error_inlining.py b/lit-test/test/interleaving/error_inlining.py new file mode 100644 index 000000000..c34959a51 --- /dev/null +++ b/lit-test/test/interleaving/error_inlining.py @@ -0,0 +1,16 @@ +# 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 parallel: + f() + # CHECK-L: ${LINE:+1}: fatal: it is not possible to interleave this function call within a 'with parallel:' statement because the compiler could not prove that the same function would always be called + x() From 66b1388a634c82165e2c34cbc155289cb15a2e5f Mon Sep 17 00:00:00 2001 From: whitequark Date: Tue, 24 Nov 2015 02:59:15 +0800 Subject: [PATCH 369/369] transforms.artiq_ir_generator: never put TVars in dicts. A TVar looks just like whatever it points to, but it does not compare equal, nor is its hash the same. --- artiq/compiler/transforms/artiq_ir_generator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/artiq/compiler/transforms/artiq_ir_generator.py b/artiq/compiler/transforms/artiq_ir_generator.py index 376d3a48a..e29949299 100644 --- a/artiq/compiler/transforms/artiq_ir_generator.py +++ b/artiq/compiler/transforms/artiq_ir_generator.py @@ -1625,7 +1625,7 @@ class ARTIQIRGenerator(algorithm.Visitor): method_key = None if isinstance(node.func, asttyped.AttributeT): attr_node = node.func - self.method_map[(attr_node.value.type, attr_node.attr)].append(insn) + 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()