forked from M-Labs/artiq
1
0
Fork 0

Merge branch 'new-py2llvm'

This commit is contained in:
whitequark 2015-11-24 03:01:54 +08:00
commit c14299dca8
236 changed files with 15395 additions and 3955 deletions

8
.gitignore vendored
View File

@ -5,6 +5,7 @@ __pycache__
*.bin
*.elf
*.fbi
*.pyc
doc/manual/_build
/build
/dist
@ -15,3 +16,10 @@ artiq/test/h5types.h5
examples/master/results
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

2
.gitmodules vendored
View File

@ -1,4 +1,4 @@
[submodule "artiq/runtime/lwip"]
path = artiq/runtime/lwip
url = git://git.savannah.nongnu.org/lwip.git
ignore = untracked
ignore = untracked

View File

@ -1,6 +1,9 @@
language: python
python:
- '3.5'
branches:
only:
- master
sudo: false
env:
global:

View File

@ -0,0 +1,2 @@
from .module import Module, Source
from .embedding import Stitcher

View File

@ -0,0 +1 @@
from .inline import inline

View File

@ -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(ir.GetAttr(call_insn.target_function(), '__closure__'),
before=call_insn)
for actual_arg, formal_arg in zip([closure] + call_insn.arguments(),
source_function.arguments):
value_map[formal_arg] = actual_arg
for source_block in source_function.basic_blocks:
target_block = ir.BasicBlock([], "i." + source_block.name)
target_function.add(target_block)
value_map[source_block] = target_block
def mapper(value):
if isinstance(value, ir.Constant):
return value
else:
return value_map[value]
for source_insn in source_function.instructions():
target_block = value_map[source_insn.basic_block]
if isinstance(source_insn, ir.Return):
if target_return_phi is not None:
target_return_phi.add_incoming(mapper(source_insn.value()), target_block)
target_insn = ir.Branch(target_successor)
elif isinstance(source_insn, ir.Phi):
target_insn = ir.Phi()
elif isinstance(source_insn, ir.Delay):
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()

View File

@ -0,0 +1,2 @@
from .domination import DominatorTree
from .devirtualization import Devirtualization

View File

@ -0,0 +1,119 @@
"""
:class:`Devirtualizer` performs method resolution at
compile time.
Devirtualization is implemented using a lattice
with three states: unknown assigned once diverges.
The lattice is computed individually for every
variable in scope as well as every
(instance type, field name) pair.
"""
from pythonparser import algorithm
from .. import asttyped, ir, types
def _advance(target_map, key, value):
if key not in target_map:
target_map[key] = value # unknown → assigned once
else:
target_map[key] = None # assigned once → diverges
class FunctionResolver(algorithm.Visitor):
def __init__(self, variable_map):
self.variable_map = variable_map
self.scope_map = dict()
self.queue = []
self.in_assign = False
self.current_scopes = []
def finalize(self):
for thunk in self.queue:
thunk()
def visit_scope(self, node):
self.current_scopes.append(node)
self.generic_visit(node)
self.current_scopes.pop()
def visit_in_assign(self, node):
self.in_assign = True
self.visit(node)
self.in_assign = False
def visit_Assign(self, node):
self.visit(node.value)
self.visit_in_assign(node.targets)
def visit_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.current_scopes[-1], node.name), node)
self.visit_scope(node)
def visit_NameT(self, node):
if self.in_assign:
# Just give up if we assign anything at all to a variable, and
# assume it diverges.
_advance(self.scope_map, (self.current_scopes[-1], node.id), None)
else:
# Look up the final value in scope_map and copy it into variable_map.
keys = [(scope, node.id) for scope in reversed(self.current_scopes)]
def thunk():
for key in keys:
if key in self.scope_map:
self.variable_map[node] = self.scope_map[key]
return
self.queue.append(thunk)
class MethodResolver(algorithm.Visitor):
def __init__(self, variable_map, method_map):
self.variable_map = variable_map
self.method_map = method_map
# embedding.Stitcher.finalize generates initialization statements
# of form "constructor.meth = meth_body".
def visit_Assign(self, node):
if node.value not in self.variable_map:
return
value = self.variable_map[node.value]
for target in node.targets:
if isinstance(target, asttyped.AttributeT):
if types.is_constructor(target.value.type):
instance_type = target.value.type.instance
elif types.is_instance(target.value.type):
instance_type = target.value.type
else:
continue
_advance(self.method_map, (instance_type, target.attr), value)
class Devirtualization:
def __init__(self):
self.variable_map = dict()
self.method_map = dict()
def visit(self, node):
function_resolver = FunctionResolver(self.variable_map)
function_resolver.visit(node)
function_resolver.finalize()
method_resolver = MethodResolver(self.variable_map, self.method_map)
method_resolver.visit(node)

View File

@ -0,0 +1,137 @@
"""
:class:`DominatorTree` computes the dominance relation over
control flow graphs.
See http://www.cs.rice.edu/~keith/EMBED/dom.pdf.
"""
class GenericDominatorTree:
def __init__(self):
self._assign_names()
self._compute()
def _traverse_in_postorder(self):
raise NotImplementedError
def _prev_block_names(self, block):
raise NotImplementedError
def _assign_names(self):
postorder = self._traverse_in_postorder()
self._start_name = len(postorder) - 1
self._block_of_name = postorder
self._name_of_block = {}
for block_name, block in enumerate(postorder):
self._name_of_block[block] = block_name
def _intersect(self, block_name_1, block_name_2):
finger_1, finger_2 = block_name_1, block_name_2
while finger_1 != finger_2:
while finger_1 < finger_2:
finger_1 = self._doms[finger_1]
while finger_2 < finger_1:
finger_2 = self._doms[finger_2]
return finger_1
def _compute(self):
self._doms = {}
# Start block dominates itself.
self._doms[self._start_name] = self._start_name
# We don't yet know what blocks dominate all other blocks.
for block_name in range(self._start_name):
self._doms[block_name] = None
changed = True
while changed:
changed = False
# For all blocks except start block, in reverse postorder...
for block_name in reversed(range(self._start_name)):
# Select a new immediate dominator from the blocks we have
# already processed, and remember all others.
# We've already processed at least one previous block because
# of the graph traverse order.
new_idom, prev_block_names = None, []
for prev_block_name in self._prev_block_names(block_name):
if new_idom is None and self._doms[prev_block_name] is not None:
new_idom = prev_block_name
else:
prev_block_names.append(prev_block_name)
# Find a common previous block
for prev_block_name in prev_block_names:
if self._doms[prev_block_name] is not None:
new_idom = self._intersect(prev_block_name, new_idom)
if self._doms[block_name] != new_idom:
self._doms[block_name] = new_idom
changed = True
def immediate_dominator(self, block):
return self._block_of_name[self._doms[self._name_of_block[block]]]
def dominators(self, block):
yield block
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]
class DominatorTree(GenericDominatorTree):
def __init__(self, function):
self.function = function
super().__init__()
def _traverse_in_postorder(self):
postorder = []
visited = set()
def visit(block):
visited.add(block)
for next_block in block.successors():
if next_block not in visited:
visit(next_block)
postorder.append(block)
visit(self.function.entry())
return postorder
def _prev_block_names(self, block_name):
for block in self._block_of_name[block_name].predecessors():
yield self._name_of_block[block]
class PostDominatorTree(GenericDominatorTree):
def __init__(self, function):
self.function = function
super().__init__()
def _traverse_in_postorder(self):
postorder = []
visited = set()
def visit(block):
visited.add(block)
for next_block in block.predecessors():
if next_block not in visited:
visit(next_block)
postorder.append(block)
for block in self.function.basic_blocks:
if not any(block.successors()):
visit(block)
postorder.append(None) # virtual exit block
return postorder
def _prev_block_names(self, block_name):
succ_blocks = self._block_of_name[block_name].successors()
if len(succ_blocks) > 0:
for block in succ_blocks:
yield self._name_of_block[block]
else:
yield self._start_name

View File

@ -0,0 +1,99 @@
"""
The typedtree module exports the PythonParser AST enriched with
typing information.
"""
from pythonparser import ast
class commontyped(ast.commonloc):
"""A mixin for typed AST nodes."""
_types = ("type",)
def _reprfields(self):
return self._fields + self._locs + self._types
class scoped(object):
"""
:ivar typing_env: (dict with string keys and :class:`.types.Type` values)
map of variable names to variable types
:ivar globals_in_scope: (set of string keys)
set of variables resolved as globals
"""
# Typed versions of untyped nodes
class argT(ast.arg, commontyped):
pass
class ClassDefT(ast.ClassDef):
_types = ("constructor_type",)
class FunctionDefT(ast.FunctionDef, scoped):
_types = ("signature_type",)
class ModuleT(ast.Module, scoped):
pass
class ExceptHandlerT(ast.ExceptHandler):
_fields = ("filter", "name", "body") # rename ast.ExceptHandler.type to filter
_types = ("name_type",)
class SliceT(ast.Slice, commontyped):
pass
class AttributeT(ast.Attribute, commontyped):
pass
class BinOpT(ast.BinOp, commontyped):
pass
class BoolOpT(ast.BoolOp, commontyped):
pass
class CallT(ast.Call, commontyped):
"""
:ivar iodelay: (:class:`iodelay.Expr`)
"""
class CompareT(ast.Compare, commontyped):
pass
class DictT(ast.Dict, commontyped):
pass
class DictCompT(ast.DictComp, commontyped, scoped):
pass
class EllipsisT(ast.Ellipsis, commontyped):
pass
class GeneratorExpT(ast.GeneratorExp, commontyped, scoped):
pass
class IfExpT(ast.IfExp, commontyped):
pass
class LambdaT(ast.Lambda, commontyped, scoped):
pass
class ListT(ast.List, commontyped):
pass
class ListCompT(ast.ListComp, commontyped, scoped):
pass
class NameT(ast.Name, commontyped):
pass
class NameConstantT(ast.NameConstant, commontyped):
pass
class NumT(ast.Num, commontyped):
pass
class SetT(ast.Set, commontyped):
pass
class SetCompT(ast.SetComp, commontyped, scoped):
pass
class StrT(ast.Str, commontyped):
pass
class StarredT(ast.Starred, commontyped):
pass
class SubscriptT(ast.Subscript, commontyped):
pass
class TupleT(ast.Tuple, commontyped):
pass
class UnaryOpT(ast.UnaryOp, commontyped):
pass
class YieldT(ast.Yield, commontyped):
pass
class YieldFromT(ast.YieldFrom, commontyped):
pass
# Novel typed nodes
class CoerceT(ast.expr, commontyped):
_fields = ('value',) # other_value deliberately not in _fields
class QuoteT(ast.expr, commontyped):
_fields = ('value',)

245
artiq/compiler/builtins.py Normal file
View File

@ -0,0 +1,245 @@
"""
The :mod:`builtins` module contains the builtin Python
and ARTIQ types, such as int or float.
"""
from collections import OrderedDict
from . import types
# Types
class TNone(types.TMono):
def __init__(self):
super().__init__("NoneType")
class TBool(types.TMono):
def __init__(self):
super().__init__("bool")
@staticmethod
def zero():
return False
@staticmethod
def one():
return True
class TInt(types.TMono):
def __init__(self, width=None):
if width is None:
width = types.TVar()
super().__init__("int", {"width": width})
@staticmethod
def zero():
return 0
@staticmethod
def one():
return 1
def TInt32():
return TInt(types.TValue(32))
def TInt64():
return TInt(types.TValue(64))
class TFloat(types.TMono):
def __init__(self):
super().__init__("float")
@staticmethod
def zero():
return 0.0
@staticmethod
def one():
return 1.0
class TStr(types.TMono):
def __init__(self):
super().__init__("str")
class TList(types.TMono):
def __init__(self, elt=None):
if elt is None:
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})
self.attributes = OrderedDict([
("start", elt),
("stop", elt),
("step", elt),
])
class TException(types.TMono):
# All exceptions share the same internal layout:
# * Pointer to the unique global with the name of the exception (str)
# (which also serves as the EHABI type_info).
# * File, line and column where it was raised (str, int, int).
# * Message, which can contain substitutions {0}, {1} and {2} (str).
# * Three 64-bit integers, parameterizing the message (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))),
("__func__", TStr()),
("__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)
def fn_bool():
return types.TConstructor(TBool())
def fn_int():
return types.TConstructor(TInt())
def fn_float():
return types.TConstructor(TFloat())
def fn_str():
return types.TConstructor(TStr())
def fn_list():
return types.TConstructor(TList())
def fn_Exception():
return types.TExceptionConstructor(TException("Exception"))
def fn_IndexError():
return types.TExceptionConstructor(TException("IndexError"))
def fn_ValueError():
return types.TExceptionConstructor(TException("ValueError"))
def fn_ZeroDivisionError():
return types.TExceptionConstructor(TException("ZeroDivisionError"))
def fn_range():
return types.TBuiltinFunction("range")
def fn_len():
return types.TBuiltinFunction("len")
def fn_round():
return types.TBuiltinFunction("round")
def fn_print():
return types.TBuiltinFunction("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")
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):
return types.is_mono(typ, "NoneType")
def is_bool(typ):
return types.is_mono(typ, "bool")
def is_int(typ, width=None):
if width is not None:
return types.is_mono(typ, "int", width=width)
else:
return types.is_mono(typ, "int")
def get_int_width(typ):
if is_int(typ):
return types.get_value(typ.find()["width"])
def is_float(typ):
return types.is_mono(typ, "float")
def is_str(typ):
return types.is_mono(typ, "str")
def is_numeric(typ):
typ = typ.find()
return isinstance(typ, types.TMono) and \
typ.name in ('int', 'float')
def is_list(typ, elt=None):
if elt is not None:
return types.is_mono(typ, "list", elt=elt)
else:
return types.is_mono(typ, "list")
def is_range(typ, elt=None):
if elt is not None:
return types.is_mono(typ, "range", {"elt": elt})
else:
return types.is_mono(typ, "range")
def is_exception(typ, name=None):
if name is None:
return isinstance(typ.find(), TException)
else:
return isinstance(typ.find(), TException) and \
typ.name == name
def is_iterable(typ):
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"].find()
def is_collection(typ):
typ = typ.find()
return isinstance(typ, types.TTuple) or \
types.is_mono(typ, "list")
def is_allocated(typ):
return not (is_none(typ) or is_bool(typ) or is_int(typ) or
is_float(typ) or is_range(typ) or
types._is_pointer(typ) or types.is_function(typ) or
types.is_c_function(typ) or types.is_rpc_function(typ) or
types.is_method(typ) or types.is_tuple(typ) or
types.is_value(typ))

676
artiq/compiler/embedding.py Normal file
View File

@ -0,0 +1,676 @@
"""
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 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 Levenshtein import jaro_winkler
from ..language import core as language_core
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]
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 = ""
self.source_buffer = source.Buffer(self.source, "<synthesized>")
self.type_map, self.value_map = type_map, value_map
self.quote_function = quote_function
self.expanded_from = expanded_from
def finalize(self):
self.source_buffer.source = self.source
return self.source_buffer
def _add(self, fragment):
range_from = len(self.source)
self.source += fragment
range_to = len(self.source)
return source.Range(self.source_buffer, range_from, range_to,
expanded_from=self.expanded_from)
def quote(self, value):
"""Construct an AST fragment equal to `value`."""
if value is None:
typ = builtins.TNone()
return asttyped.NameConstantT(value=value, type=typ,
loc=self._add(repr(value)))
elif 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)):
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, 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)))
elif isinstance(value, list):
begin_loc = self._add("[")
elts = []
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=builtins.TList(),
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=loc)
else:
quote_loc = self._add('`')
repr_loc = self._add(repr(value))
unquote_loc = self._add('`')
loc = quote_loc.join(unquote_loc)
if isinstance(value, type):
typ = value
else:
typ = type(value)
if typ in self.type_map:
instance_type, constructor_type = self.type_map[typ]
else:
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)
constructor_type.attributes['__objectid__'] = builtins.TInt(types.TValue(32))
instance_type.constructor = constructor_type
self.type_map[typ] = instance_type, constructor_type
if isinstance(value, type):
self.value_map[constructor_type].append((value, loc))
return asttyped.QuoteT(value=value, type=constructor_type,
loc=loc)
else:
self.value_map[instance_type].append((value, loc))
return asttyped.QuoteT(value=value, type=instance_type,
loc=loc)
def call(self, 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(), iodelay=None,
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)
self.globals = globals
self.env_stack.append(self.globals)
self.host_environment = host_environment
self.quote = quote
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:
return self.quote(self.host_environment[node.id], node.loc)
else:
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):
super().__init__(engine)
self.value_map = value_map
self.quote = quote
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.
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, object_loc.expanded_from)
def proxy_diagnostic(diag):
note = diagnostic.Diagnostic("note",
"while inferring 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)
if node.attr not in attributes:
# We just figured out what the type should be. Add it.
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 '{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]),
"attr": node.attr},
object_loc)
self.engine.process(diag)
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())
else:
# We don't care; only types change during inference.
pass
fields = node._fields
if hasattr(node, '_types'):
fields = fields + node._types
return hash(tuple(freeze(getattr(node, field_name)) for field_name in fields))
class Stitcher:
def __init__(self, engine=None):
if engine is None:
self.engine = diagnostic.Engine(all_errors_are_fatal=True)
else:
self.engine = engine
self.name = ""
self.typedtree = []
self.inject_at = 0
self.prelude = prelude.globals()
self.globals = {}
self.functions = {}
self.object_map = ObjectMap()
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,
quote=self._quote)
hasher = TypedtreeHasher()
# Iterate inference to fixed point.
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
# 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("", "<synthesized>")
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)))
# Extract function source.
embedded_function = function.artiq_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
# Extract function environment.
host_environment = dict()
host_environment.update(embedded_function.__globals__)
cells = embedded_function.__closure__
cell_names = embedded_function.__code__.co_freevars
host_environment.update({var: cells[index] for index, var in enumerate(cell_names)})
# Find out how indented we are.
initial_whitespace = re.search(r"^\s*", source_code).group(0)
initial_indent = len(initial_whitespace.expandtabs())
# Parse.
source_buffer = source.Buffer(source_code, filename, first_line)
lexer = source_lexer.Lexer(source_buffer, version=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__)
# 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.
asttyped_rewriter = StitchingASTTypedRewriter(
engine=self.engine, prelude=self.prelude,
globals=self.globals, host_environment=host_environment,
quote=self._quote)
return asttyped_rewriter.visit(function_node)
def _function_loc(self, function):
filename = function.__code__.co_filename
line = function.__code__.co_firstlineno
name = function.__code__.co_name
source_line = linecache.getline(filename, line)
while source_line.lstrip().startswith("@"):
line += 1
source_line = linecache.getline(filename, line)
if "<lambda>" in function.__qualname__:
column = 0 # can't get column of lambda
else:
column = re.search("def", source_line).start(0)
source_buffer = source.Buffer(source_line, filename, line)
return source.Range(source_buffer, column, column)
def _call_site_note(self, call_loc, is_syscall):
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 []
def _extract_annot(self, function, annot, kind, call_loc, is_syscall):
if not isinstance(annot, types.Type):
diag = diagnostic.Diagnostic("error",
"type annotation for {kind}, '{annot}', is not an ARTIQ type",
{"kind": kind, "annot": repr(annot)},
self._function_loc(function),
notes=self._call_site_note(call_loc, is_syscall))
self.engine.process(diag)
return types.TVar()
else:
return annot
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,
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),
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.
# This is tricky, because the default value might not have
# a well-defined type in APython.
# In this case, we bail out, but mention why we do it.
ast = self._quote(param.default, None)
def proxy_diagnostic(diag):
note = diagnostic.Diagnostic("note",
"expanded from here while trying to infer a type for an"
" unannotated optional argument '{argument}' from its default value",
{"argument": param.name},
self._function_loc(function))
diag.notes.append(note)
note = self._call_site_note(loc, is_syscall)
if note:
diag.notes += 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)
return ast.type
else:
# Let the rest of the program decide.
return types.TVar()
def _quote_foreign_function(self, function, loc, syscall):
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,
is_syscall=syscall is not None)
elif syscall is None:
optarg_types[param.name] = self._type_of_param(function, loc, param,
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),
notes=self._call_site_note(loc, is_syscall=True))
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, is_syscall=syscall is not None)
elif syscall is None:
ret_type = builtins.TNone()
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))
self.engine.process(diag)
ret_type = types.TVar()
if syscall is None:
function_type = types.TRPCFunction(arg_types, optarg_types, ret_type,
service=self.object_map.store(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)
self.globals[function_name] = function_type
self.functions[function] = function_name
return function_name, function_type
def _quote_function(self, function, loc):
if function in self.functions:
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:
# 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._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.
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_foreign_function(function, loc,
syscall=None)
def _quote(self, value, loc):
synthesizer = self._synthesizer(loc)
node = synthesizer.quote(value)
synthesizer.finalize()
return node

249
artiq/compiler/iodelay.py Normal file
View File

@ -0,0 +1,249 @@
"""
The :mod:`iodelay` module contains the classes describing
the statically inferred RTIO delay arising from executing
a function.
"""
from functools import reduce
class Expr:
def __add__(lhs, rhs):
assert isinstance(rhs, Expr)
return Add(lhs, rhs)
__iadd__ = __add__
def __sub__(lhs, rhs):
assert isinstance(rhs, Expr)
return Sub(lhs, rhs)
__isub__ = __sub__
def __mul__(lhs, rhs):
assert isinstance(rhs, Expr)
return Mul(lhs, rhs)
__imul__ = __mul__
def __truediv__(lhs, rhs):
assert isinstance(rhs, Expr)
return TrueDiv(lhs, rhs)
__itruediv__ = __truediv__
def __floordiv__(lhs, rhs):
assert isinstance(rhs, Expr)
return FloorDiv(lhs, rhs)
__ifloordiv__ = __floordiv__
def __ne__(lhs, rhs):
return not (lhs == rhs)
def free_vars(self):
return set()
def fold(self, vars=None):
return self
class Const(Expr):
_priority = 1
def __init__(self, value):
assert isinstance(value, (int, float))
self.value = value
def __str__(self):
return str(self.value)
def __eq__(lhs, rhs):
return rhs.__class__ == lhs.__class__ and lhs.value == rhs.value
def eval(self, env):
return self.value
class Var(Expr):
_priority = 1
def __init__(self, name):
assert isinstance(name, str)
self.name = name
def __str__(self):
return self.name
def __eq__(lhs, rhs):
return rhs.__class__ == lhs.__class__ and lhs.name == rhs.name
def free_vars(self):
return {self.name}
def fold(self, vars=None):
if vars is not None and self.name in vars:
return vars[self.name]
else:
return self
class Conv(Expr):
_priority = 1
def __init__(self, operand, ref_period):
assert isinstance(operand, Expr)
assert isinstance(ref_period, float)
self.operand, self.ref_period = operand, ref_period
def __eq__(lhs, rhs):
return rhs.__class__ == lhs.__class__ and \
lhs.ref_period == rhs.ref_period and \
lhs.operand == rhs.operand
def free_vars(self):
return self.operand.free_vars()
class MUToS(Conv):
def __str__(self):
return "mu->s({})".format(self.operand)
def eval(self, env):
return self.operand.eval(env) * self.ref_period
def fold(self, vars=None):
operand = self.operand.fold(vars)
if isinstance(operand, Const):
return Const(operand.value * self.ref_period)
else:
return MUToS(operand, ref_period=self.ref_period)
class SToMU(Conv):
def __str__(self):
return "s->mu({})".format(self.operand)
def eval(self, env):
return int(self.operand.eval(env) / self.ref_period)
def fold(self, vars=None):
operand = self.operand.fold(vars)
if isinstance(operand, Const):
return Const(int(operand.value / self.ref_period))
else:
return SToMU(operand, ref_period=self.ref_period)
class BinOp(Expr):
def __init__(self, lhs, rhs):
self.lhs, self.rhs = lhs, rhs
def __str__(self):
lhs = "({})".format(self.lhs) if self.lhs._priority > self._priority else str(self.lhs)
rhs = "({})".format(self.rhs) if self.rhs._priority > self._priority else str(self.rhs)
return "{} {} {}".format(lhs, self._symbol, rhs)
def __eq__(lhs, rhs):
return rhs.__class__ == lhs.__class__ and lhs.lhs == rhs.lhs and lhs.rhs == rhs.rhs
def eval(self, env):
return self.__class__._op(self.lhs.eval(env), self.rhs.eval(env))
def free_vars(self):
return self.lhs.free_vars() | self.rhs.free_vars()
def _fold_binop(self, lhs, rhs):
if isinstance(lhs, Const) and lhs.__class__ == rhs.__class__:
return Const(self.__class__._op(lhs.value, rhs.value))
elif isinstance(lhs, (MUToS, SToMU)) and lhs.__class__ == rhs.__class__:
return lhs.__class__(self.__class__(lhs.operand, rhs.operand),
ref_period=lhs.ref_period).fold()
else:
return self.__class__(lhs, rhs)
def fold(self, vars=None):
return self._fold_binop(self.lhs.fold(vars), self.rhs.fold(vars))
class BinOpFixpoint(BinOp):
def _fold_binop(self, lhs, rhs):
if isinstance(lhs, Const) and lhs.value == self._fixpoint:
return rhs
elif isinstance(rhs, Const) and rhs.value == self._fixpoint:
return lhs
else:
return super()._fold_binop(lhs, rhs)
class Add(BinOpFixpoint):
_priority = 2
_symbol = "+"
_op = lambda a, b: a + b
_fixpoint = 0
class Mul(BinOpFixpoint):
_priority = 1
_symbol = "*"
_op = lambda a, b: a * b
_fixpoint = 1
class Sub(BinOp):
_priority = 2
_symbol = "-"
_op = lambda a, b: a - b
def _fold_binop(self, lhs, rhs):
if isinstance(rhs, Const) and rhs.value == 0:
return lhs
else:
return super()._fold_binop(lhs, rhs)
class Div(BinOp):
def _fold_binop(self, lhs, rhs):
if isinstance(rhs, Const) and rhs.value == 1:
return lhs
else:
return super()._fold_binop(lhs, rhs)
class TrueDiv(Div):
_priority = 1
_symbol = "/"
_op = lambda a, b: a / b if b != 0 else 0
class FloorDiv(Div):
_priority = 1
_symbol = "//"
_op = lambda a, b: a // b if b != 0 else 0
class Max(Expr):
_priority = 1
def __init__(self, operands):
assert isinstance(operands, list)
assert all([isinstance(operand, Expr) for operand in operands])
assert operands != []
self.operands = operands
def __str__(self):
return "max({})".format(", ".join([str(operand) for operand in self.operands]))
def __eq__(lhs, rhs):
return rhs.__class__ == lhs.__class__ and lhs.operands == rhs.operands
def free_vars(self):
return reduce(lambda a, b: a | b, [operand.free_vars() for operand in self.operands])
def eval(self, env):
return max([operand.eval() for operand in self.operands])
def fold(self, vars=None):
consts, exprs = [], []
for operand in self.operands:
operand = operand.fold(vars)
if isinstance(operand, Const):
consts.append(operand.value)
elif operand not in exprs:
exprs.append(operand)
if len(consts) > 0:
exprs.append(Const(max(consts)))
if len(exprs) == 1:
return exprs[0]
else:
return Max(exprs)
def is_const(expr, value=None):
expr = expr.fold()
if value is None:
return isinstance(expr, Const)
else:
return isinstance(expr, Const) and expr.value == value
def is_zero(expr):
return is_const(expr, 0)

1374
artiq/compiler/ir.py Normal file

File diff suppressed because it is too large Load Diff

94
artiq/compiler/module.py Normal file
View File

@ -0,0 +1,94 @@
"""
The :class:`Module` class encapsulates a single Python module,
which corresponds to a single ARTIQ translation unit (one LLVM
bitcode file and one object file, unless LTO is used).
A :class:`Module` can be created from a typed AST.
The :class:`Source` class parses a single source file or
string and infers types for it using a trivial :module:`prelude`.
"""
import os
from pythonparser import source, diagnostic, parse_buffer
from . import prelude, types, transforms, analyses, validators
class Source:
def __init__(self, source_buffer, engine=None):
if engine is None:
self.engine = diagnostic.Engine(all_errors_are_fatal=True)
else:
self.engine = engine
self.object_map = None
self.name, _ = os.path.splitext(os.path.basename(source_buffer.name))
asttyped_rewriter = transforms.ASTTypedRewriter(engine=engine,
prelude=prelude.globals())
inferencer = transforms.Inferencer(engine=engine)
self.parsetree, self.comments = parse_buffer(source_buffer, engine=engine)
self.typedtree = asttyped_rewriter.visit(self.parsetree)
self.globals = asttyped_rewriter.globals
inferencer.visit(self.typedtree)
@classmethod
def from_string(cls, source_string, name="input.py", first_line=1, engine=None):
return cls(source.Buffer(source_string + "\n", name, first_line), engine=engine)
@classmethod
def from_filename(cls, filename, engine=None):
with open(filename) as f:
return cls(source.Buffer(f.read(), filename, 1), engine=engine)
class Module:
def __init__(self, src, ref_period=1e-6):
self.engine = src.engine
self.object_map = src.object_map
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)
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)
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
int_monomorphizer.visit(src.typedtree)
inferencer.visit(src.typedtree)
monomorphism_validator.visit(src.typedtree)
escape_validator.visit(src.typedtree)
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)
interleaver.process(self.artiq_ir)
def build_llvm_ir(self, target):
"""Compile the module to LLVM IR for the specified target."""
llvm_ir_generator = transforms.LLVMIRGenerator(engine=self.engine,
module_name=self.name, target=target,
object_map=self.object_map)
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."""
if self.name != "":
return self.name + ".__modinit__"
else:
return "__modinit__"
def __repr__(self):
printer = types.TypePrinter()
globals = ["%s: %s" % (var, printer.name(self.globals[var])) for var in self.globals]
return "<artiq.compiler.Module %s {\n %s\n}>" % (repr(self.name), ",\n ".join(globals))

44
artiq/compiler/prelude.py Normal file
View File

@ -0,0 +1,44 @@
"""
The :mod:`prelude` module contains the initial global environment
in which ARTIQ kernels are evaluated.
"""
from . import builtins
def globals():
return {
# Value constructors
"bool": builtins.fn_bool(),
"int": builtins.fn_int(),
"float": builtins.fn_float(),
"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 context managers
"parallel": builtins.fn_parallel(),
"sequential": builtins.fn_sequential(),
# 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(),
}

169
artiq/compiler/targets.py Normal file
View File

@ -0,0 +1,169 @@
import os, sys, tempfile, subprocess
from artiq.compiler import types
from llvmlite_artiq import ir as ll, binding as llvm
llvm.initialize()
llvm.initialize_all_targets()
llvm.initialize_all_asmprinters()
class RunTool:
def __init__(self, pattern, **tempdata):
self.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
generated by the ARTIQ compiler will be deployed.
: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)
Name of a formatted print functions (with the signature of ``printf``)
provided by the target, e.g. ``"printf"``.
"""
triple = "unknown"
data_layout = ""
features = []
print_function = "printf"
def __init__(self):
self.llcontext = ll.Context()
def compile(self, module):
"""Compile the module to a relocatable object for this target."""
if os.getenv("ARTIQ_DUMP_SIG"):
print("====== MODULE_SIGNATURE DUMP ======", file=sys.stderr)
print(module, file=sys.stderr)
if os.getenv("ARTIQ_DUMP_IR"):
print("====== ARTIQ IR DUMP ======", file=sys.stderr)
type_printer = types.TypePrinter()
for function in module.artiq_ir:
print(function.as_entity(type_printer), 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
llpassmgr = llvm.create_module_pass_manager()
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(["+{}".format(f) for f in 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 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)
if filename == "??":
continue
# 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__()
self.triple = llvm.get_default_triple()
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"
features = ["mul", "div", "ffl1", "cmov", "addc"]
print_function = "lognonl"

View File

@ -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)

View File

@ -0,0 +1,35 @@
import sys, os
from artiq.master.databases import DeviceDB
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
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', 'dmgr': dmgr}
exec(testcase_code, testcase_vars)
try:
core = dmgr.get("core")
if compile_only:
core.compile(testcase_vars["entrypoint"], (), {})
else:
core.run(testcase_vars["entrypoint"], (), {})
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()

View File

@ -0,0 +1,83 @@
import sys, fileinput, os
from pythonparser import source, diagnostic, algorithm, parse_buffer
from .. import prelude, types
from ..transforms import ASTTypedRewriter, Inferencer, IntMonomorphizer
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] == "+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):
print("\n".join(diag.render(only_line=True)))
if diag.level == "fatal":
exit()
else:
def process_diagnostic(diag):
print("\n".join(diag.render()))
if diag.level in ("fatal", "error"):
exit(1)
engine = diagnostic.Engine()
engine.process = process_diagnostic
buf = source.Buffer("".join(fileinput.input()).expandtabs(),
os.path.basename(fileinput.filename()))
parsed, comments = parse_buffer(buf, engine=engine)
typed = ASTTypedRewriter(engine=engine, prelude=prelude.globals()).visit(parsed)
Inferencer(engine=engine).visit(typed)
if monomorphize:
IntMonomorphizer(engine=engine).visit(typed)
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()

View File

@ -0,0 +1,19 @@
import sys, fileinput
from pythonparser import diagnostic
from .. import Module, Source
def main():
def process_diagnostic(diag):
print("\n".join(diag.render()))
if diag.level in ("fatal", "error"):
exit(1)
engine = diagnostic.Engine()
engine.process = process_diagnostic
mod = Module(Source.from_string("".join(fileinput.input()).expandtabs(), engine=engine))
for fn in mod.artiq_ir:
print(fn)
if __name__ == "__main__":
main()

View File

@ -0,0 +1,35 @@
import os, sys, fileinput, ctypes
from pythonparser import diagnostic
from llvmlite_artiq import binding as llvm
from .. import Module, Source
from ..targets import NativeTarget
def main():
libartiq_support = os.getenv('LIBARTIQ_SUPPORT')
if libartiq_support is not None:
llvm.load_library_permanently(libartiq_support)
def process_diagnostic(diag):
print("\n".join(diag.render()))
if diag.level in ("fatal", "error"):
exit(1)
engine = diagnostic.Engine()
engine.process = process_diagnostic
source = "".join(fileinput.input())
source = source.replace("#ARTIQ#", "")
mod = Module(Source.from_string(source.expandtabs(), engine=engine))
target = NativeTarget()
llmod = mod.build_llvm_ir(target)
llparsedmod = llvm.parse_assembly(str(llmod))
llparsedmod.verify()
llmachine = llvm.Target.from_triple(target.triple).create_target_machine()
lljit = llvm.create_mcjit_compiler(llparsedmod, llmachine)
llmain = lljit.get_pointer_to_global(llparsedmod.get_function(llmod.name + ".__modinit__"))
ctypes.CFUNCTYPE(None)(llmain)()
if __name__ == "__main__":
main()

View File

@ -0,0 +1,30 @@
import sys, fileinput
from pythonparser import diagnostic
from llvmlite_artiq import ir as ll
from .. import Module, Source
from ..targets import NativeTarget
def main():
def process_diagnostic(diag):
print("\n".join(diag.render()))
if diag.level in ("fatal", "error"):
exit(1)
engine = diagnostic.Engine()
engine.process = process_diagnostic
mod = Module(Source.from_string("".join(fileinput.input()).expandtabs(), engine=engine))
target = NativeTarget()
llmod = mod.build_llvm_ir(target=target)
# Add main so that the result can be executed with lli
llmain = ll.Function(llmod, ll.FunctionType(ll.VoidType(), []), "main")
llbuilder = ll.IRBuilder(llmain.append_basic_block("entry"))
llbuilder.call(llmod.get_global(llmod.name + ".__modinit__"), [])
llbuilder.ret_void()
print(llmod)
if __name__ == "__main__":
main()

View File

@ -0,0 +1,37 @@
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) == 2:
print("Expected exactly one module filename", file=sys.stderr)
exit(1)
def process_diagnostic(diag):
print("\n".join(diag.render()), file=sys.stderr)
if diag.level in ("fatal", "error"):
exit(1)
engine = diagnostic.Engine()
engine.process = process_diagnostic
# Make sure everything's valid
filename = sys.argv[1]
with open(filename) as f:
code = f.read()
source = Source.from_string(code, filename, engine=engine)
module = Module(source)
benchmark(lambda: Source.from_string(code, filename),
"ARTIQ parsing and inference")
benchmark(lambda: Module(source),
"ARTIQ transforms and validators")
benchmark(lambda: OR1KTarget().compile_and_link([module]),
"LLVM optimization and linking")
if __name__ == "__main__":
main()

View File

@ -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()

View File

@ -0,0 +1,30 @@
import sys, os
from pythonparser import diagnostic
from .. import Module, Source
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(Source.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()

View File

@ -0,0 +1,43 @@
import sys, fileinput
from pythonparser import diagnostic
from .. import types, iodelay, Module, Source
def main():
if len(sys.argv) > 1 and sys.argv[1] == "+diag":
del sys.argv[1]
diag = True
def process_diagnostic(diag):
print("\n".join(diag.render(only_line=True)))
if diag.level == "fatal":
exit()
else:
diag = False
def process_diagnostic(diag):
print("\n".join(diag.render(colored=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 types.is_indeterminate_delay(typ.delay):
process_diagnostic(typ.delay.find().cause)
print(repr(mod))
except:
if not diag: raise
if __name__ == "__main__":
main()

View File

@ -0,0 +1,8 @@
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
from .interleaver import Interleaver

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,496 @@
"""
:class:`ASTTypedRewriter` rewrites a parsetree (:mod:`pythonparser.ast`)
to a typedtree (:mod:`..asttyped`).
"""
from collections import OrderedDict
from pythonparser import ast, algorithm, diagnostic
from .. import asttyped, types, builtins
# This visitor will be called for every node with a scope,
# i.e.: class, function, comprehension, lambda
class LocalExtractor(algorithm.Visitor):
def __init__(self, env_stack, engine):
super().__init__()
self.env_stack = env_stack
self.engine = engine
self.in_root = False
self.in_assign = False
self.typing_env = OrderedDict()
# which names are global have to be recorded in the current scope
self.global_ = set()
# which names are nonlocal only affects whether the current scope
# gets a new binding or not, so we throw this away
self.nonlocal_ = set()
# parameters can't be declared as global or nonlocal
self.params = set()
def visit_in_assign(self, node, in_assign):
try:
old_in_assign, self.in_assign = self.in_assign, in_assign
return self.visit(node)
finally:
self.in_assign = old_in_assign
def visit_Assign(self, node):
self.visit(node.value)
self.visit_in_assign(node.targets, in_assign=True)
def visit_For(self, node):
self.visit(node.iter)
self.visit_in_assign(node.target, in_assign=True)
self.visit(node.body)
self.visit(node.orelse)
def visit_withitem(self, node):
self.visit(node.context_expr)
self.visit_in_assign(node.optional_vars, in_assign=True)
def visit_comprehension(self, node):
self.visit(node.iter)
self.visit_in_assign(node.target, in_assign=True)
self.visit(node.ifs)
def visit_generator(self, node):
if self.in_root:
return
self.in_root = True
self.visit(list(reversed(node.generators)))
self.visit(node.elt)
visit_ListComp = visit_generator
visit_SetComp = visit_generator
visit_GeneratorExp = visit_generator
def visit_DictComp(self, node):
if self.in_root:
return
self.in_root = True
self.visit(list(reversed(node.generators)))
self.visit(node.key)
self.visit(node.value)
def visit_root(self, node):
if self.in_root:
return
self.in_root = True
self.generic_visit(node)
visit_Module = visit_root # don't look at inner scopes
visit_ClassDef = visit_root
visit_Lambda = visit_root
def visit_FunctionDef(self, node):
if self.in_root:
self._assignable(node.name)
self.visit_root(node)
def _assignable(self, name):
assert name is not None
if name not in self.typing_env and name not in self.nonlocal_:
self.typing_env[name] = types.TVar()
def visit_arg(self, node):
if node.arg in self.params:
diag = diagnostic.Diagnostic("error",
"duplicate parameter '{name}'", {"name": node.arg},
node.loc)
self.engine.process(diag)
return
self._assignable(node.arg)
self.params.add(node.arg)
def visit_Name(self, node):
if self.in_assign:
# code like:
# x = 1
# def f():
# x = 1
# creates a new binding for x in f's scope
self._assignable(node.id)
def visit_Attribute(self, node):
self.visit_in_assign(node.value, in_assign=False)
def visit_Subscript(self, node):
self.visit_in_assign(node.value, in_assign=False)
self.visit_in_assign(node.slice, in_assign=False)
def _check_not_in(self, name, names, curkind, newkind, loc):
if name in names:
diag = diagnostic.Diagnostic("error",
"name '{name}' cannot be {curkind} and {newkind} simultaneously",
{"name": name, "curkind": curkind, "newkind": newkind}, loc)
self.engine.process(diag)
return True
return False
def visit_Global(self, node):
for name, loc in zip(node.names, node.name_locs):
if self._check_not_in(name, self.nonlocal_, "nonlocal", "global", loc) or \
self._check_not_in(name, self.params, "a parameter", "global", loc):
continue
self.global_.add(name)
if len(self.env_stack) == 1:
self._assignable(name) # already in global scope
else:
if name not in self.env_stack[1]:
self.env_stack[1][name] = types.TVar()
self.typing_env[name] = self.env_stack[1][name]
def visit_Nonlocal(self, node):
for name, loc in zip(node.names, node.name_locs):
if self._check_not_in(name, self.global_, "global", "nonlocal", loc) or \
self._check_not_in(name, self.params, "a parameter", "nonlocal", loc):
continue
# nonlocal does not search prelude and global scopes
found = False
for outer_env in reversed(self.env_stack[2:]):
if name in outer_env:
found = True
break
if not found:
diag = diagnostic.Diagnostic("error",
"cannot declare name '{name}' as nonlocal: it is not bound in any outer scope",
{"name": name},
loc, [node.keyword_loc])
self.engine.process(diag)
continue
self.nonlocal_.add(name)
def visit_ExceptHandler(self, node):
self.visit(node.type)
if node.name is not None:
self._assignable(node.name)
for stmt in node.body:
self.visit(stmt)
class ASTTypedRewriter(algorithm.Transformer):
"""
:class:`ASTTypedRewriter` converts an untyped AST to a typed AST
where all type fields of non-literals are filled with fresh type variables,
and type fields of literals are filled with corresponding types.
:class:`ASTTypedRewriter` also discovers the scope of variable bindings
via :class:`LocalExtractor`.
"""
def __init__(self, engine, prelude):
self.engine = engine
self.globals = None
self.env_stack = [prelude]
self.in_class = None
def _try_find_name(self, name):
for typing_env in reversed(self.env_stack):
if name in typing_env:
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",
"undefined variable '{name}'", {"name":name}, loc)
self.engine.process(diag)
# Visitors that replace node with a typed node
#
def visit_Module(self, node):
extractor = LocalExtractor(env_stack=self.env_stack, engine=self.engine)
extractor.visit(node)
node = asttyped.ModuleT(
typing_env=extractor.typing_env, globals_in_scope=extractor.global_,
body=node.body, loc=node.loc)
self.globals = node.typing_env
try:
self.env_stack.append(node.typing_env)
return self.generic_visit(node)
finally:
self.env_stack.pop()
def visit_FunctionDef(self, node):
extractor = LocalExtractor(env_stack=self.env_stack, engine=self.engine)
extractor.visit(node)
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_ClassDef(self, node):
if any(node.bases) or any(node.keywords) or \
node.starargs is not None or node.kwargs is not None:
diag = diagnostic.Diagnostic("error",
"inheritance is not supported", {},
node.lparen_loc.join(node.rparen_loc))
self.engine.process(diag)
for child in node.body:
if isinstance(child, (ast.Assign, ast.FunctionDef, ast.Pass)):
continue
diag = diagnostic.Diagnostic("fatal",
"class body must contain only assignments and function definitions", {},
child.loc)
self.engine.process(diag)
if node.name in self.env_stack[-1]:
diag = diagnostic.Diagnostic("fatal",
"variable '{name}' is already defined", {"name":node.name}, node.name_loc)
self.engine.process(diag)
extractor = LocalExtractor(env_stack=self.env_stack, engine=self.engine)
extractor.visit(node)
# Now we create two types.
# The first type is the type of instances created by the constructor.
# Its attributes are those of the class environment, but wrapped
# appropriately so that they are linked to the class from which they
# originate.
instance_type = types.TInstance(node.name, OrderedDict())
# The second type is the type of the constructor itself (in other words,
# the class object): it is simply a singleton type that has the class
# environment as attributes.
constructor_type = types.TConstructor(instance_type)
constructor_type.attributes = extractor.typing_env
instance_type.constructor = constructor_type
self.env_stack[-1][node.name] = constructor_type
node = asttyped.ClassDefT(
constructor_type=constructor_type,
name=node.name,
bases=self.visit(node.bases), keywords=self.visit(node.keywords),
starargs=self.visit(node.starargs), kwargs=self.visit(node.kwargs),
body=node.body,
decorator_list=self.visit(node.decorator_list),
keyword_loc=node.keyword_loc, name_loc=node.name_loc,
lparen_loc=node.lparen_loc, star_loc=node.star_loc,
dstar_loc=node.dstar_loc, rparen_loc=node.rparen_loc,
colon_loc=node.colon_loc, at_locs=node.at_locs,
loc=node.loc)
try:
old_in_class, self.in_class = self.in_class, node
return self.generic_visit(node)
finally:
self.in_class = old_in_class
def visit_arg(self, node):
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_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)
def visit_NameConstant(self, node):
if node.value is True or node.value is False:
typ = builtins.TBool()
elif node.value is None:
typ = builtins.TNone()
return asttyped.NameConstantT(type=typ, value=node.value, loc=node.loc)
def visit_Tuple(self, node):
node = self.generic_visit(node)
return asttyped.TupleT(type=types.TTuple([x.type for x in node.elts]),
elts=node.elts, ctx=node.ctx, loc=node.loc)
def visit_List(self, node):
node = self.generic_visit(node)
node = asttyped.ListT(type=builtins.TList(),
elts=node.elts, ctx=node.ctx,
begin_loc=node.begin_loc, end_loc=node.end_loc, loc=node.loc)
return self.visit(node)
def visit_Attribute(self, node):
node = self.generic_visit(node)
node = asttyped.AttributeT(type=types.TVar(),
value=node.value, attr=node.attr, ctx=node.ctx,
dot_loc=node.dot_loc, attr_loc=node.attr_loc, loc=node.loc)
return self.visit(node)
def visit_Slice(self, node):
node = self.generic_visit(node)
node = asttyped.SliceT(type=types.TVar(),
lower=node.lower, upper=node.upper, step=node.step,
bound_colon_loc=node.bound_colon_loc,
step_colon_loc=node.step_colon_loc,
loc=node.loc)
return self.visit(node)
def visit_Subscript(self, node):
node = self.generic_visit(node)
node = asttyped.SubscriptT(type=types.TVar(),
value=node.value, slice=node.slice, ctx=node.ctx,
begin_loc=node.begin_loc, end_loc=node.end_loc, loc=node.loc)
return self.visit(node)
def visit_BoolOp(self, node):
node = self.generic_visit(node)
node = asttyped.BoolOpT(type=types.TVar(),
op=node.op, values=node.values,
op_locs=node.op_locs, loc=node.loc)
return self.visit(node)
def visit_UnaryOp(self, node):
node = self.generic_visit(node)
node = asttyped.UnaryOpT(type=types.TVar(),
op=node.op, operand=node.operand,
loc=node.loc)
return self.visit(node)
def visit_BinOp(self, node):
node = self.generic_visit(node)
node = asttyped.BinOpT(type=types.TVar(),
left=node.left, op=node.op, right=node.right,
loc=node.loc)
return self.visit(node)
def visit_Compare(self, node):
node = self.generic_visit(node)
node = asttyped.CompareT(type=types.TVar(),
left=node.left, ops=node.ops, comparators=node.comparators,
loc=node.loc)
return self.visit(node)
def visit_IfExp(self, node):
node = self.generic_visit(node)
node = asttyped.IfExpT(type=types.TVar(),
test=node.test, body=node.body, orelse=node.orelse,
if_loc=node.if_loc, else_loc=node.else_loc, loc=node.loc)
return self.visit(node)
def visit_ListComp(self, node):
extractor = LocalExtractor(env_stack=self.env_stack, engine=self.engine)
extractor.visit(node)
node = asttyped.ListCompT(
typing_env=extractor.typing_env, globals_in_scope=extractor.global_,
type=types.TVar(),
elt=node.elt, generators=node.generators,
begin_loc=node.begin_loc, end_loc=node.end_loc, loc=node.loc)
try:
self.env_stack.append(node.typing_env)
return self.generic_visit(node)
finally:
self.env_stack.pop()
def visit_Call(self, node):
node = self.generic_visit(node)
node = asttyped.CallT(type=types.TVar(), iodelay=None,
func=node.func, args=node.args, keywords=node.keywords,
starargs=node.starargs, kwargs=node.kwargs,
star_loc=node.star_loc, dstar_loc=node.dstar_loc,
begin_loc=node.begin_loc, end_loc=node.end_loc, loc=node.loc)
return node
def visit_Lambda(self, node):
extractor = LocalExtractor(env_stack=self.env_stack, engine=self.engine)
extractor.visit(node)
node = asttyped.LambdaT(
typing_env=extractor.typing_env, globals_in_scope=extractor.global_,
type=types.TVar(),
args=node.args, body=node.body,
lambda_loc=node.lambda_loc, colon_loc=node.colon_loc, loc=node.loc)
try:
self.env_stack.append(node.typing_env)
return self.generic_visit(node)
finally:
self.env_stack.pop()
def visit_ExceptHandler(self, node):
node = self.generic_visit(node)
if node.name is not None:
name_type = self._find_name(node.name, node.name_loc)
else:
name_type = types.TVar()
node = asttyped.ExceptHandlerT(
name_type=name_type,
filter=node.type, name=node.name, body=node.body,
except_loc=node.except_loc, as_loc=node.as_loc, name_loc=node.name_loc,
colon_loc=node.colon_loc, loc=node.loc)
return node
def visit_Raise(self, node):
node = self.generic_visit(node)
if node.cause:
diag = diagnostic.Diagnostic("error",
"'raise from' syntax is not supported", {},
node.from_loc)
self.engine.process(diag)
return node
# Unsupported visitors
#
def visit_unsupported(self, node):
diag = diagnostic.Diagnostic("fatal",
"this syntax is not supported", {},
node.loc)
self.engine.process(diag)
# expr
visit_Dict = visit_unsupported
visit_DictComp = visit_unsupported
visit_Ellipsis = visit_unsupported
visit_GeneratorExp = visit_unsupported
visit_Set = visit_unsupported
visit_SetComp = visit_unsupported
visit_Starred = visit_unsupported
visit_Yield = visit_unsupported
visit_YieldFrom = visit_unsupported
# stmt
visit_Delete = visit_unsupported
visit_Import = visit_unsupported
visit_ImportFrom = visit_unsupported

View File

@ -0,0 +1,43 @@
"""
: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 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):
# 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()

File diff suppressed because it is too large Load Diff

View File

@ -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))

View File

@ -0,0 +1,161 @@
"""
:class:`Interleaver` reorders requests to the RTIO core so that
the timestamp would always monotonically nondecrease.
"""
from pythonparser import diagnostic
from .. import types, builtins, ir, iodelay
from ..analyses import domination
from ..algorithms import inline
def delay_free_subgraph(root, limit):
visited = set()
queue = root.successors()
while len(queue) > 0:
block = queue.pop()
visited.add(block)
if block is limit:
continue
if isinstance(block.terminator(), ir.Delay):
return False
for successor in block.successors():
if successor not in visited:
queue.append(successor)
return True
def iodelay_of_block(block):
terminator = block.terminator()
if isinstance(terminator, ir.Delay):
# We should be able to fold everything without free variables.
folded_expr = terminator.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
def process(self, functions):
for func in functions:
self.process_function(func)
def process_function(self, func):
for insn in func.instructions():
if isinstance(insn, ir.Delay):
if any(insn.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 not isinstance(insn, ir.Parallel):
continue
# 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 time_after_block(pair):
index, block = pair
return source_times[index] + iodelay_of_block(block)
# Always prefer impure blocks (with calls) to pure blocks, because
# impure blocks may expand with smaller delays appearing, and in
# case of a tie, if a pure block is preferred, this would violate
# the timeline monotonicity.
available_source_blocks = list(filter(is_impure_delay_block, source_blocks))
if not any(available_source_blocks):
available_source_blocks = source_blocks
index, source_block = min(enumerate(available_source_blocks), key=time_after_block)
source_block_delay = iodelay_of_block(source_block)
new_target_time = source_times[index] + source_block_delay
target_time_delta = new_target_time - target_time
assert target_time_delta >= 0
target_terminator = target_block.terminator()
if isinstance(target_terminator, ir.Parallel):
target_terminator.replace_with(ir.Branch(source_block))
else:
assert isinstance(target_terminator, (ir.Delay, ir.Branch))
target_terminator.set_target(source_block)
source_terminator = source_block.terminator()
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
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:
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
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
new_source_block = postdom_tree.immediate_dominator(source_block)
assert (new_source_block is not None)
assert delay_free_subgraph(source_block, new_source_block)
if new_source_block == interleave_until:
# We're finished with this branch.
del source_blocks[index]
del source_times[index]
else:
source_blocks[index] = new_source_block
source_times[index] = new_target_time

View File

@ -0,0 +1,286 @@
"""
:class:`IODelayEstimator` calculates the amount of time
elapsed from the point of view of the RTIO core for
every function.
"""
from pythonparser import ast, algorithm, diagnostic
from .. import types, iodelay, builtins, asttyped
class _UnknownDelay(Exception):
pass
class _IndeterminateDelay(Exception):
def __init__(self, cause):
self.cause = cause
class IODelayEstimator(algorithm.Visitor):
def __init__(self, engine, ref_period):
self.engine = engine
self.ref_period = ref_period
self.changed = False
self.current_delay = iodelay.Const(0)
self.current_args = None
self.current_goto = None
self.current_return = None
def evaluate(self, node, abort):
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 _IndeterminateDelay(diag)
def visit_fixpoint(self, node):
while True:
self.changed = False
self.visit(node)
if not self.changed:
return
def visit_ModuleT(self, node):
try:
for stmt in node.body:
try:
self.visit(stmt)
except _UnknownDelay:
pass # more luck next time?
except _IndeterminateDelay:
pass # we don't care; module-level code is never interleaved
def visit_function(self, args, body, typ, loc):
old_args, self.current_args = self.current_args, args
old_return, self.current_return = self.current_return, None
old_delay, self.current_delay = self.current_delay, iodelay.Const(0)
try:
self.visit(body)
if not iodelay.is_zero(self.current_delay) and self.current_return is not None:
self.abort("only return statement at the end of the function "
"can be interleaved", self.current_return.loc)
delay = types.TFixedDelay(self.current_delay.fold())
except _IndeterminateDelay as error:
delay = types.TIndeterminateDelay(error.cause)
self.current_delay = old_delay
self.current_return = old_return
self.current_args = old_args
if types.is_indeterminate_delay(delay) and types.is_indeterminate_delay(typ.delay):
# Both delays indeterminate; no point in unifying since that will
# replace the lazy and more specific error with an eager and more generic
# error (unification error of delay(?) with delay(?), which is useless).
return
try:
old_delay = typ.delay.find()
typ.delay.unify(delay)
if typ.delay.find() != old_delay:
self.changed = True
except types.UnificationError as e:
printer = types.TypePrinter()
diag = diagnostic.Diagnostic("fatal",
"delay {delaya} was inferred for this function, but its delay is already "
"constrained externally to {delayb}",
{"delaya": printer.name(delay), "delayb": printer.name(typ.delay)},
loc)
self.engine.process(diag)
def visit_FunctionDefT(self, node):
self.visit(node.args.defaults)
self.visit(node.args.kw_defaults)
# We can only handle return in tail position.
if isinstance(node.body[-1], ast.Return):
body = node.body[:-1]
else:
body = node.body
self.visit_function(node.args, body, node.signature_type.find(), node.loc)
def visit_LambdaT(self, node):
self.visit_function(node.args, node.body, node.type.find(), node.loc)
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"):
try:
delays = []
for stmt in node.body:
old_delay, self.current_delay = self.current_delay, iodelay.Const(0)
self.visit(stmt)
delays.append(self.current_delay)
self.current_delay = old_delay
if any(delays):
self.current_delay += iodelay.Max(delays)
except _IndeterminateDelay as error:
# Interleave failures inside `with` statements are hard failures,
# since there's no chance that the code will never actually execute
# inside a `with` statement after all.
self.engine.process(error.cause)
elif len(node.items) == 1 and types.is_builtin(context_expr.type, "sequential"):
self.visit(node.body)
else:
self.abort("with statement cannot be interleaved", node.loc)
def visit_CallT(self, node):
typ = node.func.type.find()
def abort(notes):
self.abort("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)
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)
call_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.find()
if types.is_var(delay):
raise _UnknownDelay()
elif delay.is_indeterminate():
note = diagnostic.Diagnostic("note",
"function called here", {},
node.loc)
cause = delay.cause
cause = diagnostic.Diagnostic(cause.level, cause.reason, cause.arguments,
cause.location, cause.highlights,
cause.notes + [note])
raise _IndeterminateDelay(cause)
elif delay.is_fixed():
args = {}
for kw_node in node.keywords:
args[kw_node.arg] = kw_node.value
for arg_name, arg_node in zip(list(typ.args)[offset:], node.args):
args[arg_name] = arg_node
free_vars = delay.duration.free_vars()
call_delay = delay.duration.fold(
{ 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

File diff suppressed because it is too large Load Diff

657
artiq/compiler/types.py Normal file
View File

@ -0,0 +1,657 @@
"""
The :mod:`types` module contains the classes describing the types
in :mod:`asttyped`.
"""
import string
from collections import OrderedDict
from pythonparser import diagnostic
from . import iodelay
class UnificationError(Exception):
def __init__(self, typea, typeb):
self.typea, self.typeb = typea, typeb
def genalnum():
ident = ["a"]
while True:
yield "".join(ident)
pos = len(ident) - 1
while pos >= 0:
cur_n = string.ascii_lowercase.index(ident[pos])
if cur_n < 25:
ident[pos] = string.ascii_lowercase[cur_n + 1]
break
else:
ident[pos] = "a"
pos -= 1
if pos < 0:
ident = ["a"] + ident
def _freeze(dict_):
return tuple((key, dict_[key]) for key in dict_)
def _map_find(elts):
if isinstance(elts, list):
return [x.find() for x in elts]
elif isinstance(elts, dict):
return {k: elts[k].find() for k in elts}
else:
assert False
class Type(object):
def __str__(self):
return TypePrinter().name(self)
class TVar(Type):
"""
A type variable.
In effect, the classic union-find data structure is intrusively
folded into this class.
"""
def __init__(self):
self.parent = self
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 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 "<artiq.compiler.types.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.
:class:`TMono` is supposed to be subclassed by builtin types,
unlike all other :class:`Type` descendants. Similarly,
instances of :class:`TMono` should never be allocated directly,
as that will break the type-sniffing code in :mod:`builtins`.
"""
attributes = OrderedDict()
def __init__(self, name, params={}):
assert isinstance(params, (dict, OrderedDict))
self.name, self.params = name, OrderedDict(sorted(params.items()))
def find(self):
return self
def unify(self, other):
if isinstance(other, TMono) and self.name == other.name:
assert self.params.keys() == other.params.keys()
for param in self.params:
self.params[param].unify(other.params[param])
elif isinstance(other, TVar):
other.unify(self)
else:
raise UnificationError(self, other)
def fold(self, accum, fn):
for param in self.params:
accum = self.params[param].fold(accum, fn)
return fn(accum, self)
def __repr__(self):
return "artiq.compiler.types.TMono(%s, %s)" % (repr(self.name), repr(self.params))
def __getitem__(self, param):
return self.params[param]
def __eq__(self, other):
return isinstance(other, TMono) and \
self.name == other.name and \
_map_find(self.params) == _map_find(other.params)
def __ne__(self, other):
return not (self == other)
def __hash__(self):
return hash((self.name, _freeze(self.params)))
class TTuple(Type):
"""
A tuple type.
:ivar elts: (list of :class:`Type`) elements
"""
attributes = OrderedDict()
def __init__(self, elts=[]):
self.elts = elts
def find(self):
return self
def unify(self, other):
if isinstance(other, TTuple) and len(self.elts) == len(other.elts):
for selfelt, otherelt in zip(self.elts, other.elts):
selfelt.unify(otherelt)
elif isinstance(other, TVar):
other.unify(self)
else:
raise UnificationError(self, other)
def fold(self, accum, fn):
for elt in self.elts:
accum = elt.fold(accum, fn)
return fn(accum, self)
def __repr__(self):
return "artiq.compiler.types.TTuple(%s)" % repr(self.elts)
def __eq__(self, other):
return isinstance(other, TTuple) and \
_map_find(self.elts) == _map_find(other.elts)
def __ne__(self, other):
return not (self == other)
class _TPointer(TMono):
def __init__(self):
super().__init__("pointer")
class TFunction(Type):
"""
A function type.
:ivar args: (:class:`collections.OrderedDict` of string to :class:`Type`)
mandatory arguments
:ivar optargs: (:class:`collections.OrderedDict` of string to :class:`Type`)
optional arguments
:ivar ret: (:class:`Type`)
return type
:ivar delay: (:class:`Type`)
RTIO delay
"""
attributes = OrderedDict([
('__code__', _TPointer()),
('__closure__', _TPointer()),
])
def __init__(self, args, optargs, ret):
assert isinstance(args, OrderedDict)
assert isinstance(optargs, OrderedDict)
assert isinstance(ret, Type)
self.args, self.optargs, self.ret = args, optargs, ret
self.delay = TVar()
def arity(self):
return len(self.args) + len(self.optargs)
def 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(list(self.args.values()) + list(self.optargs.values()),
list(other.args.values()) + list(other.optargs.values())):
selfarg.unify(otherarg)
self.ret.unify(other.ret)
self.delay.unify(other.delay)
elif isinstance(other, TVar):
other.unify(self)
else:
raise UnificationError(self, other)
def fold(self, accum, fn):
for arg in self.args:
accum = self.args[arg].fold(accum, fn)
for optarg in self.optargs:
accum = self.optargs[optarg].fold(accum, fn)
accum = self.ret.fold(accum, fn)
return fn(accum, self)
def __repr__(self):
return "artiq.compiler.types.TFunction({}, {}, {})".format(
repr(self.args), repr(self.optargs), repr(self.ret))
def __eq__(self, other):
return isinstance(other, TFunction) and \
_map_find(self.args) == _map_find(other.args) and \
_map_find(self.optargs) == _map_find(other.optargs)
def __ne__(self, other):
return not (self == other)
def __hash__(self):
return hash((_freeze(self.args), _freeze(self.optargs), self.ret))
class TRPCFunction(TFunction):
"""
A function type of a remote function.
:ivar service: (int) RPC service number
"""
attributes = OrderedDict()
def __init__(self, args, optargs, ret, service):
super().__init__(args, optargs, ret)
self.service = service
self.delay = TFixedDelay(iodelay.Const(0))
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 TCFunction(TFunction):
"""
A function type of a runtime-provided C function.
:ivar name: (str) C function name
"""
attributes = OrderedDict()
def __init__(self, args, ret, name):
super().__init__(args, OrderedDict(), ret)
self.name = name
self.delay = TFixedDelay(iodelay.Const(0))
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
type is treated specially according to its name.
"""
def __init__(self, name):
assert isinstance(name, str)
self.name = name
self.attributes = OrderedDict()
def find(self):
return self
def unify(self, other):
if self != other:
raise UnificationError(self, other)
def fold(self, accum, fn):
return fn(accum, self)
def __repr__(self):
return "artiq.compiler.types.{}({})".format(type(self).__name__, repr(self.name))
def __eq__(self, other):
return isinstance(other, TBuiltin) and \
self.name == other.name
def __ne__(self, other):
return not (self == other)
def __hash__(self):
return hash(self.name)
class TBuiltinFunction(TBuiltin):
"""
A type of a builtin function.
"""
class TConstructor(TBuiltin):
"""
A type of a constructor of a class, e.g. ``list``.
Note that this is not the same as the type of an instance of
the class, which is ``TMono("list", ...)`` (or a descendant).
:ivar instance: (:class:`Type`)
the type of the instance created by this constructor
"""
def __init__(self, instance):
assert isinstance(instance, TMono)
super().__init__(instance.name)
self.instance = instance
class TExceptionConstructor(TConstructor):
"""
A type of a constructor of an exception, e.g. ``Exception``.
Note that this is not the same as the type of an instance of
the class, which is ``TMono("Exception", ...)``.
"""
class TInstance(TMono):
"""
A type of an instance of a user-defined class.
:ivar constructor: (:class:`TConstructor`)
the type of the constructor with which this instance
was created
"""
def __init__(self, name, attributes):
assert isinstance(attributes, OrderedDict)
super().__init__(name)
self.attributes = attributes
def __repr__(self):
return "artiq.compiler.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):
"""
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 isinstance(other, TVar):
other.unify(self)
elif self != other:
raise UnificationError(self, other)
def fold(self, accum, fn):
return fn(accum, self)
def __repr__(self):
return "artiq.compiler.types.TValue(%s)" % repr(self.value)
def __eq__(self, other):
return isinstance(other, TValue) and \
self.value == other.value
def __ne__(self, other):
return not (self == other)
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)
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 \
(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:
return isinstance(typ, TTuple) and \
elts == typ.elts
else:
return isinstance(typ, TTuple)
def _is_pointer(typ):
return isinstance(typ.find(), _TPointer)
def is_function(typ):
return isinstance(typ.find(), TFunction)
def is_rpc_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:
return isinstance(typ, TBuiltin)
else:
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:
return isinstance(typ, TExceptionConstructor) and \
typ.name == name
else:
return isinstance(typ, TExceptionConstructor)
def is_instance(typ, name=None):
typ = typ.find()
if name is not None:
return isinstance(typ, TInstance) and \
typ.name == name
else:
return isinstance(typ, TInstance)
def is_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)
def get_value(typ):
typ = typ.find()
if isinstance(typ, TVar):
return None
elif isinstance(typ, TValue):
return typ.value
else:
assert False
def is_delay(typ):
return isinstance(typ.find(), TDelay)
def is_fixed_delay(typ):
return is_delay(typ) and typ.find().is_fixed()
def is_indeterminate_delay(typ):
return is_delay(typ) and typ.find().is_indeterminate()
class TypePrinter(object):
"""
A class that prints types using Python-like syntax and gives
type variables sequential alphabetic names.
"""
def __init__(self):
self.gen = genalnum()
self.map = {}
self.recurse_guard = set()
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, TInstance):
if typ in self.recurse_guard:
return "<instance {}>".format(typ.name)
else:
self.recurse_guard.add(typ)
attrs = ", ".join(["{}: {}".format(attr, self.name(typ.attributes[attr]))
for attr in typ.attributes])
return "<instance {} {{{}}}>".format(typ.name, attrs)
elif isinstance(typ, TMono):
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])
else:
return "(%s)" % ", ".join(list(map(self.name, typ.elts)))
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]
signature = "(%s)->%s" % (", ".join(args), self.name(typ.ret))
delay = typ.delay.find()
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):
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):
return "<function {}>".format(typ.name)
elif isinstance(typ, (TConstructor, TExceptionConstructor)):
if typ in self.recurse_guard:
return "<constructor {}>".format(typ.name)
else:
self.recurse_guard.add(typ)
attrs = ", ".join(["{}: {}".format(attr, self.name(typ.attributes[attr]))
for attr in typ.attributes])
return "<constructor {} {{{}}}>".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

View File

@ -0,0 +1,3 @@
from .monomorphism import MonomorphismValidator
from .escape import EscapeValidator
from .local_access import LocalAccessValidator

View File

@ -0,0 +1,311 @@
"""
:class:`EscapeValidator` verifies that no mutable data escapes
the region of its allocation.
"""
import functools
from pythonparser import algorithm, diagnostic
from .. import asttyped, types, builtins
def has_region(typ):
return typ.fold(False, lambda accum, typ: accum or builtins.is_allocated(typ))
class 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_sometimes_allocating(self, node):
if has_region(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):
if has_region(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
# Value lives forever
def visit_immutable(self, node):
assert not has_region(node.type)
return None
visit_NameConstantT = visit_immutable
visit_NumT = visit_immutable
visit_EllipsisT = visit_immutable
visit_UnaryOpT = visit_immutable
visit_CompareT = 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
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):
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, typing_env):
try:
old_youngest_region = self.youngest_region
self.youngest_region = region
old_youngest_env = self.youngest_env
self.youngest_env = {}
for name in typing_env:
if has_region(typing_env[name]):
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, node.typing_env)
def visit_FunctionDefT(self, node):
self.youngest_env[node.name] = self.youngest_region
self.visit_in_region(node, Region(node.loc), node.typing_env)
def visit_ClassDefT(self, node):
self.youngest_env[node.name] = self.youngest_region
self.visit_in_region(node, Region(node.loc), node.constructor_type.attributes)
# Only three ways for a pointer to escape:
# * Assigning or op-assigning it (we ensure an outlives relationship)
# * Returning it (we only allow returning values that live forever)
# * Raising it (we forbid allocating exceptions that refer to mutable data)¹
#
# Literals doesn't count: a constructed object is always
# outlived by all its constituents.
# Closures don't count: see above.
# Calling functions doesn't count: arguments never outlive
# the function body.
#
# ¹Strings are currently never allocated with a limited lifetime,
# and exceptions can only refer to strings, so we don't actually check
# this property. But we will need to, if string operations are ever added.
def visit_assignment(self, target, value, 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_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)
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)

View File

@ -0,0 +1,175 @@
"""
:class:`LocalAccessValidator` verifies that local variables
are not accessed before being used.
"""
from functools import reduce
from pythonparser import diagnostic
from .. import ir, analyses
def is_special_variable(name):
return "$" in name
class LocalAccessValidator:
def __init__(self, engine):
self.engine = engine
def process(self, functions):
for func in functions:
self.process_function(func)
def process_function(self, func):
# Find all environments and closures allocated in this func.
environments, closures = [], []
for insn in func.instructions():
if isinstance(insn, ir.Alloc) and ir.is_environment(insn.type):
environments.append(insn)
elif isinstance(insn, ir.Closure):
closures.append(insn)
# Compute initial state of interesting environments.
# Environments consisting only of internal variables (containing a ".")
# are ignored.
initial_state = {}
for env in environments:
env_state = {var: False for var in env.type.params if "." not in var}
if any(env_state):
initial_state[env] = env_state
# Traverse the acyclic graph made of basic blocks and forward edges only,
# while updating the environment state.
domtree = analyses.DominatorTree(func)
state = {}
def traverse(block):
# Have we computed the state of this block already?
if block in state:
return state[block]
# No! Which forward edges lead to this block?
# If we dominate a predecessor, it's a back edge instead.
forward_edge_preds = [pred for pred in block.predecessors()
if block not in domtree.dominators(pred)]
# Figure out what the state is before the leader
# instruction of this block.
pred_states = [traverse(pred) for pred in forward_edge_preds]
block_state = {}
if len(pred_states) > 1:
for env in initial_state:
# The variable has to be initialized in all predecessors
# in order to be initialized in this block.
def merge_state(a, b):
return {var: a[var] and b[var] for var in a}
block_state[env] = reduce(merge_state,
[state[env] for state in pred_states])
elif len(pred_states) == 1:
# The state is the same as at the terminator of predecessor.
# We'll mutate it, so copy.
pred_state = pred_states[0]
for env in initial_state:
env_state = pred_state[env]
block_state[env] = {var: env_state[var] for var in env_state}
else:
# This is the entry block.
for env in initial_state:
env_state = initial_state[env]
block_state[env] = {var: env_state[var] for var in env_state}
# Update the state based on block contents, while validating
# that no access to uninitialized variables will be done.
for insn in block.instructions:
def pred_at_fault(env, var_name):
# Find out where the uninitialized state comes from.
for pred, pred_state in zip(forward_edge_preds, pred_states):
if not pred_state[env][var_name]:
return pred
# It's the entry block and it was never initialized.
return None
set_local_in_this_frame = False
if (isinstance(insn, (ir.SetLocal, ir.GetLocal)) and
not is_special_variable(insn.var_name)):
env, var_name = insn.environment(), insn.var_name
# Make sure that the variable is defined in the scope of this function.
if env in block_state and var_name in block_state[env]:
if isinstance(insn, ir.SetLocal):
# We've just initialized it.
block_state[env][var_name] = True
set_local_in_this_frame = True
else: # isinstance(insn, ir.GetLocal)
if not block_state[env][var_name]:
# Oops, accessing it uninitialized.
self._uninitialized_access(insn, var_name,
pred_at_fault(env, var_name))
closures_to_check = []
if (isinstance(insn, (ir.SetLocal, ir.SetAttr, ir.SetElem)) and
not set_local_in_this_frame):
# Closures may escape via these mechanisms and be invoked elsewhere.
if isinstance(insn.value(), ir.Closure):
closures_to_check.append(insn.value())
if isinstance(insn, (ir.Call, ir.Invoke)):
# We can't always trace the flow of closures from point of
# definition to point of call; however, we know that, by transitiveness
# of this analysis, only closures defined in this function can contain
# uninitialized variables.
#
# Thus, enumerate the closures, and check all of them during any operation
# that may eventually result in the closure being called.
closures_to_check = closures
for closure in closures_to_check:
env = closure.environment()
# Make sure this environment has any interesting variables.
if env in block_state:
for var_name in block_state[env]:
if not block_state[env][var_name] and not is_special_variable(var_name):
# A closure would capture this variable while it is not always
# initialized. Note that this check is transitive.
self._uninitialized_access(closure, var_name,
pred_at_fault(env, var_name))
# Save the state.
state[block] = block_state
return block_state
for block in func.basic_blocks:
traverse(block)
def _uninitialized_access(self, insn, var_name, pred_at_fault):
if pred_at_fault is not None:
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)
self.engine.process(diag)

View File

@ -0,0 +1,39 @@
"""
:class:`MonomorphismValidator` verifies that all type variables have been
elided, which is necessary for code generation.
"""
from pythonparser import algorithm, diagnostic
from .. import asttyped, types, builtins
class MonomorphismValidator(algorithm.Visitor):
def __init__(self, engine):
self.engine = engine
def visit_FunctionDefT(self, node):
super().generic_visit(node)
return_type = node.signature_type.find().ret
if types.is_polymorphic(return_type):
note = diagnostic.Diagnostic("note",
"the function has return type {type}",
{"type": types.TypePrinter().name(return_type)},
node.name_loc)
diag = diagnostic.Diagnostic("error",
"the return type of this function cannot be fully inferred", {},
node.name_loc, notes=[note])
self.engine.process(diag)
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)

View File

@ -3,28 +3,16 @@ from operator import itemgetter
class Comm:
def __init__(self, dmgr):
pass
super().__init__()
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

View File

@ -1,11 +1,10 @@
import struct
import logging
import traceback
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__)
@ -13,22 +12,26 @@ logger = logging.getLogger(__name__)
class _H2DMsgType(Enum):
LOG_REQUEST = 1
IDENT_REQUEST = 2
SWITCH_CLOCK = 3
LOG_CLEAR = 2
LOAD_OBJECT = 4
RUN_KERNEL = 5
IDENT_REQUEST = 3
SWITCH_CLOCK = 4
RPC_REPLY = 6
LOAD_LIBRARY = 5
RUN_KERNEL = 6
FLASH_READ_REQUEST = 7
FLASH_WRITE_REQUEST = 8
FLASH_ERASE_REQUEST = 9
FLASH_REMOVE_REQUEST = 10
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
@ -50,9 +53,16 @@ class _D2HMsgType(Enum):
class UnsupportedDevice(Exception):
pass
class RPCReturnValueError(ValueError):
pass
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."""
@ -72,175 +82,412 @@ 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)"
" in packet {}".
format(length, self._read_length, self._read_type))
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
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
#
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_chunk(self, chunk):
self._write_buffer.append(chunk)
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_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
#
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_OBJECT)
self.write(kcode)
_, ty = self._read_header()
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 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
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))
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))
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))
def _receive_rpc_value(self, type_tag):
if type_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)
def _receive_rpc_values(self):
r = []
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)])
else:
r.append(self._receive_rpc_value(type_tag))
def _serve_rpc(self, rpc_wrapper, rpc_map, user_exception_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)
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):
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
def serve(self, rpc_map, user_exception_map):
rpc_wrapper = RPCWrapper()
while True:
_, ty = self._read_header()
if ty == _D2HMsgType.RPC_REQUEST:
self._serve_rpc(rpc_wrapper, rpc_map, user_exception_map)
elif ty == _D2HMsgType.KERNEL_EXCEPTION:
self._serve_exception(rpc_wrapper, user_exception_map)
elif ty == _D2HMsgType.KERNEL_FINISHED:
return
else:
raise IOError("Incorrect request from device: "+str(ty))
self._read_empty(_D2HMsgType.CLOCK_SWITCH_COMPLETED)
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')
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)
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(_H2DMsgType.FLASH_WRITE_REQUEST)
self._write_string(key)
self._write_bytes(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_empty(_H2DMsgType.FLASH_ERASE_REQUEST)
self._read_empty(_D2HMsgType.FLASH_OK_REPLY)
def flash_storage_remove(self, key):
self._write_header(_H2DMsgType.FLASH_REMOVE_REQUEST)
self._write_string(key)
self._write_flush()
self._read_empty(_D2HMsgType.FLASH_OK_REPLY)
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")
_rpc_sentinel = object()
# See session.c:{send,receive}_rpc_value and llvm_ir_generator.py:_rpc_tag.
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(object_map) for _ in range(length))
elif tag == "n":
return None
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 == "s":
return self._read_string()
elif tag == "l":
length = self._read_int32()
return [self._receive_rpc_value(object_map) for _ in range(length)]
elif tag == "r":
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(object_map)
elif tag == "O":
return object_map.retrieve(self._read_int32())
else:
raise IOError("Unknown RPC value tag: {}".format(repr(tag)))
def _receive_rpc_args(self, object_map):
args = []
while True:
value = self._receive_rpc_value(object_map)
if value is self._rpc_sentinel:
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, object_map):
service = self._read_int32()
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 = 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,
object_map.retrieve(service))
self._write_flush()
except core_language.ARTIQException as exn:
logger.debug("rpc service: %d %r ! %r", service, args, exn)
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__, 2)
self._write_string(filename)
self._write_int32(line)
self._write_int32(-1) # column not known
self._write_string(function)
self._write_flush()
def _serve_exception(self, symbolizer):
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())]
traceback = list(reversed(symbolizer(backtrace))) + \
[(filename, line, column, function, None)]
raise core_language.ARTIQException(name, message, params, traceback)
def serve(self, object_map, symbolizer):
while True:
self._read_header()
if self._read_type == _D2HMsgType.RPC_REQUEST:
self._serve_rpc(object_map)
elif self._read_type == _D2HMsgType.KERNEL_EXCEPTION:
self._serve_exception(symbolizer)
else:
self._read_expect(_D2HMsgType.KERNEL_FINISHED)
return

View File

@ -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
@ -27,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)

View File

@ -26,6 +26,7 @@ def set_keepalive(sock, after_idle, interval, max_fails):
class Comm(CommGeneric):
def __init__(self, dmgr, host, port=1381):
super().__init__()
self.host = host
self.port = port

View File

@ -1,49 +1,37 @@
import os
from pythonparser import diagnostic
from artiq.language.core import *
from artiq.language.units import ns
from artiq.language.types import *
from artiq.language.units import *
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))
class CompileError(Exception):
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__, "..", "..")), "<artiq>")
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)
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):
pass
@syscall
def rtio_get_counter() -> TInt64:
raise NotImplementedError("syscall not simulated")
class Core:
"""Core device driver.
@ -66,79 +54,46 @@ 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)
stitcher.finalize()
fold_constants(func_def)
debug_unparse("fold_constants_1", func_def)
module = Module(stitcher, ref_period=self.ref_period)
target = OR1KTarget()
unroll_loops(func_def, 500)
debug_unparse("unroll_loops", func_def)
library = target.compile_and_link([module])
stripped_library = target.strip(library)
interleave(func_def)
debug_unparse("interleave", func_def)
return stitcher.object_map, stripped_library, \
lambda addresses: target.symbolize(library, addresses)
except diagnostic.Error as error:
raise CompileError(error.diagnostic) from error
lower_time(func_def)
debug_unparse("lower_time", func_def)
def run(self, function, args, kwargs):
object_map, kernel_library, symbolizer = self.compile(function, args, kwargs)
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):
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
self.comm.load(kernel_library)
self.comm.run()
self.comm.serve(object_map, symbolizer)
@kernel
def get_rtio_counter_mu(self):
"""Return the current value of the hardware RTIO counter."""
return syscall("rtio_get_counter")
return rtio_get_counter()
@kernel
def break_realtime(self):
"""Set the timeline to the current value of the hardware RTIO counter
plus a margin of 125000 machine units."""
min_now = syscall("rtio_get_counter") + 125000
min_now = rtio_get_counter() + 125000
if now_mu() < min_now:
at_mu(min_now)

View File

@ -1,4 +1,5 @@
from artiq.language.core import *
from artiq.language.types import *
from artiq.language.units import *
@ -9,6 +10,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
@ -37,13 +56,13 @@ class DDSBus:
The time of execution of the DDS commands is the time of entering the
batch (as closely as hardware permits)."""
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."""
syscall("dds_batch_exit")
dds_batch_exit()
class _DDSGeneric:
@ -105,7 +124,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):
@ -144,8 +163,7 @@ class _DDSGeneric:
"""
if phase_mode == _PHASE_MODE_DEFAULT:
phase_mode = self.phase_mode
syscall("dds_set", now_mu(), self.channel, frequency,
phase, phase_mode, amplitude)
dds_set(now_mu(), self.channel, frequency, phase, phase_mode, amplitude)
@kernel
def set(self, frequency, phase=0.0, phase_mode=_PHASE_MODE_DEFAULT,

View File

@ -0,0 +1,41 @@
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."""
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.
"""

View File

@ -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

View File

@ -1,212 +1,13 @@
import os
import llvmlite_artiq.ir as ll
import llvmlite_artiq.binding as llvm
class SourceLoader:
def __init__(self, runtime_root):
self.runtime_root = runtime_root
from artiq.py2llvm import base_types, fractions, lists
from artiq.language import units
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()
llvm.initialize()
llvm.initialize_all_targets()
llvm.initialize_all_asmprinters()
_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": "Iiiiii: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)
# 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:
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)
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 "<Runtime {}>".format(self.cpu_type)
artiq_root = os.path.join(os.path.dirname(__file__), "..", "..")
source_loader = SourceLoader(os.path.join(artiq_root, "soc", "runtime"))

View File

@ -1,85 +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 RTIOCollisionError(RuntimeException):
"""Raised when an event is submitted on a given channel with the same
coarse timestamp as the previous one but with a different fine timestamp.
Coarse timestamps correspond to the RTIO system clock (typically around
125MHz) whereas fine timestamps correspond to the RTIO SERDES clock
(typically around 1GHz).
The offending event is discarded and the RTIO core keeps operating.
"""
eid = 5
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 = 6
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 = 7
exception_map = {e.eid: e for e in globals().values()
if inspect.isclass(e)
and issubclass(e, RuntimeException)
and hasattr(e, "eid")}

View File

@ -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:
@ -13,18 +35,18 @@ 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):
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
@ -76,12 +98,12 @@ 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):
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
@ -137,7 +159,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
@ -193,8 +215,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
@ -205,7 +226,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:
@ -221,7 +242,7 @@ class TTLClockGen:
self.channel = channel
# in RTIO cycles
self.previous_timestamp = int64(0)
self.previous_timestamp = int(0, width=64)
self.acc_width = 24
@portable
@ -256,7 +277,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
@ -273,5 +294,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

View File

@ -1,10 +1,10 @@
#!/usr/bin/env python3.5
import logging
import argparse
import sys, logging, argparse
from artiq.master.databases import DeviceDB, DatasetDB
from artiq.master.worker_db import DeviceManager, DatasetManager
from artiq.coredevice.core import CompileError
from artiq.tools import *
@ -40,34 +40,35 @@ 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)
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)
except CompileError as error:
print(error.render_string(colored=True), file=sys.stderr)
return
finally:
device_mgr.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()

View File

@ -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,9 +61,10 @@ def main():
device_mgr = DeviceManager(DeviceDB(args.device_db))
try:
comm = device_mgr.get("comm")
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:
@ -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())

View File

@ -12,9 +12,11 @@ import h5py
from artiq.language.environment import EnvExperiment
from artiq.master.databases import DeviceDB, DatasetDB
from artiq.master.worker_db import DeviceManager, DatasetManager
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__)
@ -25,9 +27,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 DummyScheduler:
@ -92,7 +98,7 @@ def _build_experiment(device_mgr, dataset_mgr, args):
"for ELF kernels")
return ELFRunner(device_mgr, dataset_mgr, 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__"]
@ -122,6 +128,9 @@ def run(with_file=False):
exp_inst.prepare()
exp_inst.run()
exp_inst.analyze()
except CompileError as error:
print(error.render_string(colored=True), file=sys.stderr)
return
finally:
device_mgr.close_devices()

View File

@ -131,12 +131,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),
@ -173,9 +168,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))

View File

@ -1,7 +1,8 @@
# Copyright (C) 2014, 2015 Robert Jordens <jordens@gmail.com>
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 *
@ -9,6 +10,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__)

View File

@ -2,92 +2,168 @@
Core ARTIQ extensions to the Python language.
"""
import os, 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", "TerminationRequested",
"kernel", "portable",
"set_time_manager", "set_syscall_manager", "set_watchdog_factory",
"RuntimeException", "EncodedException"]
__all__ = ["host_int", "int",
"kernel", "portable", "syscall",
"set_time_manager", "set_watchdog_factory",
"ARTIQException",
"TerminationRequested"]
# 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)
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(self):
return self._width
def __int__(self):
return self._value
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__")
__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__")
__radd__ = _binaryop(host_int.__radd__, "__add__")
__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__")
__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__")
def round64(x):
"""Rounds to a 64-bit integer.
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))
class TerminationRequested(Exception):
"""Raised by ``pause`` when the user has requested termination."""
pass
_KernelFunctionInfo = namedtuple("_KernelFunctionInfo", "core_name k_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,26 +182,20 @@ 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(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
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):
"""This decorator marks a function for execution on the same device as its
def portable(function):
"""
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
@ -133,8 +203,30 @@ 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=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:
@ -163,22 +255,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."""
@ -251,17 +327,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
@ -286,32 +351,70 @@ def watchdog(timeout):
return _watchdog_factory(timeout)
_encoded_exceptions = dict()
class TerminationRequested(Exception):
"""Raised by ``pause`` when the user has requested termination."""
pass
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
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, traceback):
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
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
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, traceback)
return exn
first_user_eid = 1024
def __init__(self, name, message, params, traceback):
Exception.__init__(self, name, message, *params)
self.name, self.message, self.params = name, message, params
self.traceback = list(traceback)
def __str__(self):
lines = []
if type(self).__name__ == self.name:
lines.append(self.message.format(*self.params))
else:
lines.append("({}) {}".format(self.name, self.message.format(*self.params)))
lines.append("Core Device Traceback (most recent call last):")
for (filename, line, column, function, address) in self.traceback:
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:
formatted_address = ""
else:
formatted_address = " (RA=0x{:x})".format(address)
filename = filename.replace(os.path.normpath(os.path.join(os.path.dirname(__file__),
"..")), "<artiq>")
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 "<unknown>"))
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 "<unknown>"))
lines.append(" {}^".format(" " * (column - indentation)))
return "\n".join(lines)

19
artiq/language/types.py Normal file
View File

@ -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)))

View File

@ -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()

View File

@ -1,539 +0,0 @@
import ast
import llvmlite_artiq.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)

View File

@ -1,321 +0,0 @@
import llvmlite_artiq.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 "<VInt:{}>".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))

View File

@ -1,74 +0,0 @@
import ast
from copy import deepcopy
from artiq.py2llvm.ast_body import Visitor
from artiq.py2llvm import base_types
class _TypeScanner(ast.NodeVisitor):
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

View File

@ -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)

View File

@ -1,72 +0,0 @@
import llvmlite_artiq.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 "<VList:{} x{}>".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

View File

@ -1,62 +0,0 @@
import llvmlite_artiq.ir as ll
import llvmlite_artiq.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

View File

@ -1,5 +0,0 @@
import llvmlite_artiq.ir as ll
def is_terminated(basic_block):
return (basic_block.instructions
and isinstance(basic_block.instructions[-1], ll.Terminator))

View File

@ -1,94 +0,0 @@
from types import SimpleNamespace
from copy import copy
import llvmlite_artiq.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()

View File

@ -1,5 +1,5 @@
import inspect
import ast
from pythonparser import parse, ast
import llvmlite_artiq.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"

View File

@ -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)

View File

@ -0,0 +1,43 @@
def visit_With(self, node):
self.generic_visit(node)
if (isinstance(node.items[0].context_expr, ast.Call)
and node.items[0].context_expr.func.id == "watchdog"):
idname = "__watchdog_id_" + str(self.watchdog_id_counter)
self.watchdog_id_counter += 1
time = ast.BinOp(left=node.items[0].context_expr.args[0],
op=ast.Mult(),
right=ast.Num(1000))
time_int = ast.Call(
func=ast.Name("round", ast.Load()),
args=[time],
keywords=[], starargs=None, kwargs=None)
syscall_set = ast.Call(
func=ast.Name("syscall", ast.Load()),
args=[ast.Str("watchdog_set"), time_int],
keywords=[], starargs=None, kwargs=None)
stmt_set = ast.copy_location(
ast.Assign(targets=[ast.Name(idname, ast.Store())],
value=syscall_set),
node)
syscall_clear = ast.Call(
func=ast.Name("syscall", ast.Load()),
args=[ast.Str("watchdog_clear"),
ast.Name(idname, ast.Load())],
keywords=[], starargs=None, kwargs=None)
stmt_clear = ast.copy_location(ast.Expr(syscall_clear), node)
node.items[0] = ast.withitem(
context_expr=ast.Name(id="sequential",
ctx=ast.Load()),
optional_vars=None)
node.body = [
stmt_set,
ast.Try(body=node.body,
handlers=[],
orelse=[],
finalbody=[stmt_clear])
]
return node

View File

@ -3,10 +3,18 @@ include $(MISOC_DIRECTORY)/software/common.mak
PYTHON ?= python3.5
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 += -I$(LIBLWIP_DIRECTORY)/../lwip/src/include -I$(LIBLWIP_DIRECTORY) -I.
CFLAGS += -I$(MISOC_DIRECTORY)/software/include/dyld \
-I$(LIBDYLD_DIRECTORY)/include \
-I$(LIBUNWIND_DIRECTORY) \
-I$(LIBUNWIND_DIRECTORY)/../unwinder/include \
-I$(LIBLWIP_DIRECTORY)/../lwip/src/include \
-I$(LIBLWIP_DIRECTORY)
all: runtime.bin runtime.fbi
@ -19,7 +27,7 @@ all: runtime.bin runtime.fbi
runtime.elf: $(OBJECTS)
$(LD) $(LDFLAGS) \
-T $(RUNTIME_DIRECTORY)/linker.ld \
-T $(RUNTIME_DIRECTORY)/runtime.ld \
-N -o $@ \
../libbase/crt0-$(CPU).o \
$(OBJECTS) \
@ -31,28 +39,21 @@ runtime.elf: $(OBJECTS)
ksupport.elf: $(OBJECTS_KSUPPORT)
$(LD) $(LDFLAGS) \
--eh-frame-hdr \
-T $(RUNTIME_DIRECTORY)/ksupport.ld \
-N -o $@ \
../libbase/crt0-$(CPU).o \
$^ \
-L../libbase \
-L../libcompiler_rt \
-lcompiler_rt
-L../libunwind \
-L../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 $(RUNTIME_DIRECTORY)/gen_service_table.py
@echo " GEN " $@ && $(PYTHON) $(RUNTIME_DIRECTORY)/gen_service_table.py ksupport.elf > $@
$(RUNTIME_DIRECTORY)/services.c: service_table.h
services.o: $(RUNTIME_DIRECTORY)/services.c service_table.h
$(compile)
main.o: $(RUNTIME_DIRECTORY)/main.c
$(compile)
%.o: $(RUNTIME_DIRECTORY)/%.c
$(compile)
@ -62,6 +63,6 @@ main.o: $(RUNTIME_DIRECTORY)/main.c
clean:
$(RM) $(OBJECTS) $(OBJECTS_KSUPPORT)
$(RM) runtime.elf runtime.bin runtime.fbi .*~ *~
$(RM) service_table.h ksupport.elf ksupport.bin
$(RM) ksupport.elf ksupport.bin
.PHONY: all clean main.o
.PHONY: all clean

View File

@ -0,0 +1,464 @@
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <unwind.h>
#include "artiq_personality.h"
/* Logging */
#ifndef NDEBUG
#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, ...)
#endif
#define EH_FAIL(err) \
do { \
fprintf(stderr, "%s fatal: %s\n", __func__, 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:
memcpy(&result, p, sizeof(uintptr_t));
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:
{
uint16_t valu16;
memcpy(&valu16, p, sizeof(uint16_t));
result = valu16;
}
p += sizeof(uint16_t);
break;
case DW_EH_PE_udata4:
{
uint32_t valu32;
memcpy(&valu32, p, sizeof(uint32_t));
result = valu32;
}
p += sizeof(uint32_t);
break;
case DW_EH_PE_udata8:
{
uint64_t valu64;
memcpy(&valu64, p, sizeof(uint64_t));
result = valu64;
}
p += sizeof(uint64_t);
break;
case DW_EH_PE_sdata2:
{
int16_t val16;
memcpy(&val16, p, sizeof(int16_t));
result = val16;
}
p += sizeof(int16_t);
break;
case DW_EH_PE_sdata4:
{
int32_t val32;
memcpy(&val32, p, sizeof(int32_t));
result = val32;
}
p += sizeof(int32_t);
break;
case DW_EH_PE_sdata8:
{
int64_t val64;
memcpy(&val64, p, sizeof(int64_t));
result = val64;
}
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 */
#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;
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,
(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;
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.
// 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() {
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();
}
}
/* Unwinding */
// 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));
}
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);
_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)",
(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(typeInfoPtr == 0 || 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");
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),
(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;
}

View File

@ -0,0 +1,59 @@
#ifndef __ARTIQ_PERSONALITY_H
#define __ARTIQ_PERSONALITY_H
#include <stdint.h>
#include <stddef.h>
struct artiq_exception {
union {
uintptr_t typeinfo;
const char *name;
};
const char *file;
int32_t line;
int32_t column;
const char *function;
const char *message;
int64_t param[3];
};
struct artiq_backtrace_item {
intptr_t function;
intptr_t offset;
};
#ifdef __cplusplus
extern "C" {
#endif
/* Provided by the runtime */
void __artiq_raise(struct artiq_exception *artiq_exn)
__attribute__((noreturn));
void __artiq_reraise(void)
__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 = -1, \
.function = __func__, \
}; \
__artiq_raise(&exn); \
} while(0)
/* Called by the runtime */
void __artiq_terminate(struct artiq_exception *artiq_exn,
struct artiq_backtrace_item *backtrace,
size_t backtrace_size)
__attribute__((noreturn));
#ifdef __cplusplus
}
#endif
#endif /* __ARTIQ_PERSONALITY_H */

View File

@ -38,7 +38,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)

View File

@ -1,7 +1,7 @@
#include <generated/csr.h>
#include <stdio.h>
#include "exceptions.h"
#include "artiq_personality.h"
#include "rtio.h"
#include "log.h"
#include "dds.h"
@ -177,7 +177,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;
@ -189,7 +189,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);
@ -207,7 +207,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;

View File

@ -1,240 +0,0 @@
#include <string.h>
#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;i<ehdr->shnum;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;i<textrelasize;i+=sizeof(struct elf32_rela)) {
struct elf32_rela *rela;
struct elf32_sym *sym;
GET_POINTER_SAFE(rela, struct elf32_rela, textrelaoff + i);
GET_POINTER_SAFE(sym, struct elf32_sym, symtaboff + sizeof(struct elf32_sym)*ELF32_R_SYM(rela->info));
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;i<symtabsize;i+=sizeof(struct elf32_sym)) {
struct elf32_sym *sym;
GET_POINTER_SAFE(sym, struct elf32_sym, symtaboff + i);
if((ELF32_ST_TYPE(sym->info) == 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;
}

View File

@ -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 */

View File

@ -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

View File

@ -1,58 +0,0 @@
#include <generated/csr.h>
#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);
}
}

View File

@ -1,26 +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_COLLISION_ERROR = 5,
EID_RTIO_OVERFLOW = 6,
EID_DDS_BATCH_ERROR = 7
};
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 */

View File

@ -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);
}

View File

@ -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 */

View File

@ -1,65 +0,0 @@
#!/usr/bin/env python3.5
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()

View File

@ -1,126 +1,109 @@
#include <string.h>
#include <generated/csr.h>
#include <dyld.h>
#include "kloader.h"
#include "log.h"
#include "clock.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);
static int symtab_add(const char *name, void *target)
{
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++;
// 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);
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++;
}
return 1;
}
int kloader_load(void *buffer, int length)
{
if(!kernel_cpu_reset_read()) {
log("BUG: attempted to load while kernel CPU running");
return 0;
}
symtab_init();
return load_elf(
resolve_service_symbol, symtab_add,
buffer, length, (void *)KERNELCPU_PAYLOAD_ADDRESS, 4*1024*1024);
}
kernel_function kloader_find(const char *name)
{
return find_symbol(symtab, name);
}
extern char _binary_ksupport_bin_start;
extern char _binary_ksupport_bin_end;
static void start_kernel_cpu(void *addr)
{
memcpy((void *)KERNELCPU_EXEC_ADDRESS, &_binary_ksupport_bin_start,
&_binary_ksupport_bin_end - &_binary_ksupport_bin_start);
mailbox_acknowledge();
mailbox_send(addr);
// Start kernel CPU.
mailbox_send(msg);
kernel_cpu_reset_write(0);
}
void kloader_start_bridge(void)
void kloader_start_bridge()
{
start_kernel_cpu(NULL);
}
void kloader_start_user_kernel(kernel_function k)
static int load_or_start_kernel(const void *library, int run_kernel)
{
static struct dyld_info library_info;
struct msg_load_request request = {
.library = library,
.library_info = &library_info,
.run_kernel = 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;
}
if(reply->error != NULL) {
log("cannot load kernel: %s", reply->error);
return 0;
}
return 1;
}
int kloader_load_library(const void *library)
{
if(!kernel_cpu_reset_read()) {
log("BUG: attempted to start kernel CPU while already running (user kernel)");
return;
log("BUG: attempted to load kernel library while kernel CPU is running");
return 0;
}
start_kernel_cpu((void *)k);
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);
}
static int kloader_start_flash_kernel(char *key)
{
char buffer[32*1024];
unsigned int len, remain;
kernel_function k;
if(!kernel_cpu_reset_read()) {
log("BUG: attempted to start kernel CPU while already running (%s)", key);
return 0;
}
#if (defined CSR_SPIFLASH_BASE && defined SPIFLASH_PAGE_SIZE)
len = fs_read(key, buffer, sizeof(buffer), &remain);
if(len <= 0)
char buffer[32*1024];
unsigned int length, remain;
length = fs_read(key, buffer, sizeof(buffer), &remain);
if(length <= 0)
return 0;
if(remain) {
log("ERROR: %s too long", key);
log("ERROR: kernel %s is too large", key);
return 0;
}
if(!kloader_load(buffer, len)) {
log("ERROR: failed to load ELF binary (%s)", key);
return 0;
}
k = kloader_find("run");
if(!k) {
log("ERROR: failed to find entry point for ELF kernel (%s)", key);
return 0;
}
start_kernel_cpu((void *)k);
return 1;
return load_or_start_kernel(buffer, 1);
#else
return 0;
#endif
@ -145,7 +128,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;
}
@ -195,7 +178,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;
}

View File

@ -1,20 +1,23 @@
#ifndef __KLOADER_H
#define __KLOADER_H
#define KERNELCPU_EXEC_ADDRESS 0x40400000
#define KERNELCPU_PAYLOAD_ADDRESS 0x40408000
#include "artiq_personality.h"
#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(const void *code);
void kloader_filter_backtrace(struct artiq_backtrace_item *backtrace,
size_t *backtrace_size);
void kloader_start_bridge(void);
int kloader_start_startup_kernel(void);
int kloader_start_idle_kernel(void);
void kloader_start_user_kernel(kernel_function k);
void kloader_start_kernel(void);
void kloader_stop(void);
int kloader_validate_kpointer(void *p);

View File

@ -1,67 +1,253 @@
#include <stdarg.h>
#include <string.h>
#include <stdio.h>
#include "exceptions.h"
#include "bridge.h"
#include <link.h>
#include <dlfcn.h>
#include <dyld.h>
#include <unwind.h>
#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"
void ksupport_abort(void);
void exception_handler(unsigned long vect, unsigned long *sp);
void exception_handler(unsigned long vect, unsigned long *sp)
int64_t now;
/* 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;
/* 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},
{"abort", &ksupport_abort},
/* proxified syscalls */
{"now", &now},
{"watchdog_set", &watchdog_set},
{"watchdog_clear", &watchdog_clear},
{"log", &log},
{"lognonl", &lognonl},
{"send_rpc", &send_rpc},
{"recv_rpc", &recv_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 = "<ksupport>",
.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 = "<kernel>",
.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 *request = mailbox_receive();
struct msg_load_reply load_reply = {
.type = MESSAGE_TYPE_LOAD_REPLY,
.error = NULL
};
k = mailbox_receive();
if(k == NULL)
if(request == 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(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);
}
}
if(request->run_kernel) {
void (*kernel_init)() = request->library_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;
mailbox_send_and_wait(&finished_reply);
} else {
mailbox_send(&load_reply);
}
while(1);
}
long long int now_init(void);
/* 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);
}
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;
@ -72,8 +258,11 @@ 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 %d",
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,11 @@ 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 %d",
reply->type);
while(1);
}
id = reply->id;
mailbox_acknowledge();
@ -123,28 +314,56 @@ void watchdog_clear(int id)
mailbox_send_and_wait(&request);
}
int rpc(int rpc_num, ...)
void send_rpc(int service, const char *tag, ...)
{
struct msg_rpc_request request;
struct msg_rpc_reply *reply;
int eid, retval;
struct msg_rpc_send request;
request.type = MESSAGE_TYPE_RPC_REQUEST;
request.rpc_num = rpc_num;
va_start(request.args, rpc_num);
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);
}
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_REPLY)
exception_raise_params(EID_INTERNAL_ERROR, 3, 0, 0);
eid = reply->eid;
retval = reply->retval;
mailbox_acknowledge();
if(reply->type != MESSAGE_TYPE_RPC_RECV_REPLY) {
log("Malformed MESSAGE_TYPE_RPC_RECV_REQUEST reply type %d",
reply->type);
while(1);
}
if(eid != EID_NONE)
exception_raise(eid);
return retval;
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, ...)
{
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, ...)
@ -153,6 +372,7 @@ void log(const char *fmt, ...)
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);

13
artiq/runtime/ksupport.h Normal file
View File

@ -0,0 +1,13 @@
#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);
void send_rpc(int service, const char *tag, ...);
int recv_rpc(void *slot);
void lognonl(const char *fmt, ...);
void log(const char *fmt, ...);
#endif /* __KSTARTUP_H */

View File

@ -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
}
}

View File

@ -1,5 +1,7 @@
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include <console.h>
#include <generated/csr.h>
@ -8,7 +10,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 +20,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;
@ -41,9 +56,14 @@ void log_get(char *outbuf)
{
int i, j;
j = buffer_index + 1;
for(i=0;i<LOG_BUFFER_SIZE;i++) {
j = buffer_index;
for(i = 0; i < LOG_BUFFER_SIZE; i++) {
outbuf[i] = buffer[j];
j = (j + 1) % LOG_BUFFER_SIZE;
}
}
void log_clear()
{
memset(buffer, 0, sizeof(buffer));
}

View File

@ -5,9 +5,13 @@
#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, ...);
void log_get(char *outbuf);
void log_clear(void);
#endif /* __LOG_H */

View File

@ -182,12 +182,13 @@ static void serial_service(void)
session_poll((void **)&txdata, &txlen);
if(txlen > 0) {
for(i=0;i<txlen;i++)
for(i = 0; i < txlen; i++)
uart_write(txdata[i]);
session_ack_data(txlen);
session_ack_mem(txlen);
} else if(txlen < 0)
session_ack_consumed(txlen);
session_ack_sent(txlen);
} else if(txlen < 0) {
reset_serial_session(1);
}
}
static void regular_main(void)

View File

@ -2,8 +2,10 @@
#define __MESSAGES_H
#include <stdarg.h>
#include <stddef.h>
enum {
MESSAGE_TYPE_LOAD_REPLY,
MESSAGE_TYPE_NOW_INIT_REQUEST,
MESSAGE_TYPE_NOW_INIT_REPLY,
MESSAGE_TYPE_NOW_SAVE,
@ -12,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,
MESSAGE_TYPE_RPC_RECV_REQUEST,
MESSAGE_TYPE_RPC_RECV_REPLY,
MESSAGE_TYPE_LOG,
MESSAGE_TYPE_BRG_READY,
@ -33,6 +36,17 @@ struct msg_base {
/* kernel messages */
struct msg_load_request {
const void *library;
struct dyld_info *library_info;
int run_kernel;
};
struct msg_load_reply {
int type;
const char *error;
};
struct msg_now_init_reply {
int type;
long long int now;
@ -45,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 {
@ -64,21 +79,28 @@ struct msg_watchdog_clear {
int id;
};
struct msg_rpc_request {
struct msg_rpc_send {
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 eid;
int retval;
void *slot;
};
struct msg_rpc_recv_reply {
int type;
int alloc_size;
struct artiq_exception *exception;
};
struct msg_log {
int type;
const char *fmt;
int no_newline;
va_list args;
};

View File

@ -91,7 +91,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_mem(len);
session_ack_sent(len);
return ERR_OK;
}
@ -208,7 +208,7 @@ void net_server_service(void)
if(len > sndbuf)
len = sndbuf;
tcp_write(active_pcb, data, len, 0);
session_ack_data(len);
session_ack_consumed(len);
}
if(len < 0)
net_server_close(active_cs, active_pcb);

View File

@ -1,6 +1,5 @@
#include <generated/csr.h>
#include "exceptions.h"
#include "rtio.h"
void rtio_init(void)
@ -22,17 +21,20 @@ void rtio_process_exceptional_status(int 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);
}
if(status & RTIO_O_STATUS_COLLISION_ERROR) {
rtio_o_collision_error_reset_write(1);
exception_raise_params(EID_RTIO_COLLISION_ERROR,
artiq_raise_from_c("RTIOCollisionError",
"RTIO collision error at {0} mu, channel {1}",
timestamp, channel, 0);
}
}

View File

@ -2,6 +2,7 @@
#define __RTIO_H
#include <generated/csr.h>
#include "artiq_personality.h"
#define RTIO_O_STATUS_FULL 1
#define RTIO_O_STATUS_UNDERFLOW 2

View File

@ -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 = .;
}

Some files were not shown because too many files have changed in this diff Show More