From 353f454a29e239aa1a02cd6c2428620134050d2d Mon Sep 17 00:00:00 2001 From: whitequark Date: Fri, 7 Aug 2015 11:44:49 +0300 Subject: [PATCH] Add basic support for embedded functions with new compiler. --- artiq/compiler/__init__.py | 1 + artiq/compiler/asttyped.py | 2 + artiq/compiler/embedding.py | 157 ++++++++++++++++++ .../compiler/transforms/asttyped_rewriter.py | 7 +- artiq/coredevice/comm_generic.py | 23 +-- artiq/coredevice/core.py | 125 ++++---------- artiq/coredevice/exceptions.py | 33 ++++ artiq/coredevice/rpc_wrapper.py | 40 ----- artiq/coredevice/runtime.py | 78 --------- artiq/coredevice/runtime_exceptions.py | 69 -------- artiq/language/core.py | 82 +++++---- 11 files changed, 278 insertions(+), 339 deletions(-) create mode 100644 artiq/compiler/embedding.py create mode 100644 artiq/coredevice/exceptions.py delete mode 100644 artiq/coredevice/rpc_wrapper.py delete mode 100644 artiq/coredevice/runtime_exceptions.py diff --git a/artiq/compiler/__init__.py b/artiq/compiler/__init__.py index 1d187a71b..0fabfd4ad 100644 --- a/artiq/compiler/__init__.py +++ b/artiq/compiler/__init__.py @@ -1 +1,2 @@ from .module import Module, Source +from .embedding import Stitcher diff --git a/artiq/compiler/asttyped.py b/artiq/compiler/asttyped.py index e24003aba..f15726579 100644 --- a/artiq/compiler/asttyped.py +++ b/artiq/compiler/asttyped.py @@ -93,3 +93,5 @@ class YieldFromT(ast.YieldFrom, commontyped): # Novel typed nodes class CoerceT(ast.expr, commontyped): _fields = ('value',) # other_value deliberately not in _fields +class QuoteT(ast.expr, commontyped): + _fields = ('value',) diff --git a/artiq/compiler/embedding.py b/artiq/compiler/embedding.py new file mode 100644 index 000000000..a719c3a31 --- /dev/null +++ b/artiq/compiler/embedding.py @@ -0,0 +1,157 @@ +""" +The :class:`Stitcher` class allows to transparently combine compiled +Python code and Python code executed on the host system: it resolves +the references to the host objects and translates the functions +annotated as ``@kernel`` when they are referenced. +""" + +import inspect +from pythonparser import ast, source, diagnostic, parse_buffer +from . import types, builtins, asttyped, prelude +from .transforms import ASTTypedRewriter, Inferencer + + +class ASTSynthesizer: + def __init__(self): + self.source = "" + self.source_buffer = source.Buffer(self.source, "") + + def finalize(self): + self.source_buffer.source = self.source + return self.source_buffer + + def _add(self, fragment): + range_from = len(self.source) + self.source += fragment + range_to = len(self.source) + return source.Range(self.source_buffer, range_from, range_to) + + def quote(self, value): + """Construct an AST fragment equal to `value`.""" + if value in (None, True, False): + if node.value is True or node.value is False: + typ = builtins.TBool() + elif node.value is None: + typ = builtins.TNone() + return asttyped.NameConstantT(value=value, type=typ, + loc=self._add(repr(value))) + elif isinstance(value, (int, float)): + if isinstance(value, int): + typ = builtins.TInt() + elif isinstance(value, float): + typ = builtins.TFloat() + return asttyped.NumT(n=value, ctx=None, type=typ, + loc=self._add(repr(value))) + elif isinstance(value, list): + begin_loc = self._add("[") + elts = [] + for index, elt in value: + elts.append(self.quote(elt)) + if index < len(value) - 1: + self._add(", ") + end_loc = self._add("]") + return asttyped.ListT(elts=elts, ctx=None, type=types.TVar(), + begin_loc=begin_loc, end_loc=end_loc, + loc=begin_loc.join(end_loc)) + else: + raise "no" + # return asttyped.QuoteT(value=value, type=types.TVar()) + + def call(self, function_node, args, kwargs): + """ + Construct an AST fragment calling a function specified by + an AST node `function_node`, with given arguments. + """ + arg_nodes = [] + kwarg_nodes = [] + kwarg_locs = [] + + name_loc = self._add(function_node.name) + begin_loc = self._add("(") + for index, arg in enumerate(args): + arg_nodes.append(self.quote(arg)) + if index < len(args) - 1: + self._add(", ") + if any(args) and any(kwargs): + self._add(", ") + for index, kw in enumerate(kwargs): + arg_loc = self._add(kw) + equals_loc = self._add("=") + kwarg_locs.append((arg_loc, equals_loc)) + kwarg_nodes.append(self.quote(kwargs[kw])) + if index < len(kwargs) - 1: + self._add(", ") + end_loc = self._add(")") + + return asttyped.CallT( + func=asttyped.NameT(id=function_node.name, ctx=None, + type=function_node.signature_type, + loc=name_loc), + args=arg_nodes, + keywords=[ast.keyword(arg=kw, value=value, + arg_loc=arg_loc, equals_loc=equals_loc, + loc=arg_loc.join(value.loc)) + for kw, value, (arg_loc, equals_loc) + in zip(kwargs, kwarg_nodes, kwarg_locs)], + starargs=None, kwargs=None, + type=types.TVar(), + begin_loc=begin_loc, end_loc=end_loc, star_loc=None, dstar_loc=None, + loc=name_loc.join(end_loc)) + +class StitchingASTTypedRewriter(ASTTypedRewriter): + pass + +class Stitcher: + def __init__(self, engine=None): + if engine is None: + self.engine = diagnostic.Engine(all_errors_are_fatal=True) + else: + self.engine = engine + + self.asttyped_rewriter = StitchingASTTypedRewriter( + engine=self.engine, globals=prelude.globals()) + self.inferencer = Inferencer(engine=self.engine) + + self.name = "stitched" + self.typedtree = None + self.globals = self.asttyped_rewriter.globals + + self.rpc_map = {} + + def _iterate(self): + # Iterate inference to fixed point. + self.inference_finished = False + while not self.inference_finished: + self.inference_finished = True + self.inferencer.visit(self.typedtree) + + def _parse_embedded_function(self, function): + if not hasattr(function, "artiq_embedded"): + raise ValueError("{} is not an embedded function".format(repr(function))) + + # Extract function source. + embedded_function = function.artiq_embedded.function + source_code = inspect.getsource(embedded_function) + filename = embedded_function.__code__.co_filename + first_line = embedded_function.__code__.co_firstlineno + + # Parse. + source_buffer = source.Buffer(source_code, filename, first_line) + parsetree, comments = parse_buffer(source_buffer, engine=self.engine) + + # Rewrite into typed form. + typedtree = self.asttyped_rewriter.visit(parsetree) + + return typedtree, typedtree.body[0] + + def stitch_call(self, function, args, kwargs): + self.typedtree, function_node = self._parse_embedded_function(function) + + # We synthesize fake source code for the initial call so that + # diagnostics would have something meaningful to display to the user. + synthesizer = ASTSynthesizer() + call_node = synthesizer.call(function_node, args, kwargs) + synthesizer.finalize() + self.typedtree.body.append(call_node) + + self._iterate() diff --git a/artiq/compiler/transforms/asttyped_rewriter.py b/artiq/compiler/transforms/asttyped_rewriter.py index cc0de6b2c..85b96e694 100644 --- a/artiq/compiler/transforms/asttyped_rewriter.py +++ b/artiq/compiler/transforms/asttyped_rewriter.py @@ -190,10 +190,15 @@ class ASTTypedRewriter(algorithm.Transformer): self.globals = None self.env_stack = [globals] - def _find_name(self, name, loc): + def _try_find_name(self, name): for typing_env in reversed(self.env_stack): if name in typing_env: return typing_env[name] + + def _find_name(self, name, loc): + typ = self._try_find_name(name) + if typ is not None: + return typ diag = diagnostic.Diagnostic("fatal", "name '{name}' is not bound to anything", {"name":name}, loc) self.engine.process(diag) diff --git a/artiq/coredevice/comm_generic.py b/artiq/coredevice/comm_generic.py index b538c33f7..da5d91017 100644 --- a/artiq/coredevice/comm_generic.py +++ b/artiq/coredevice/comm_generic.py @@ -3,9 +3,7 @@ import logging from enum import Enum from fractions import Fraction -from artiq.coredevice import runtime_exceptions from artiq.language import core as core_language -from artiq.coredevice.rpc_wrapper import RPCWrapper logger = logging.getLogger(__name__) @@ -198,35 +196,28 @@ class CommGeneric: else: r.append(self._receive_rpc_value(type_tag)) - def _serve_rpc(self, rpc_wrapper, rpc_map, user_exception_map): + def _serve_rpc(self, rpc_map): rpc_num = struct.unpack(">l", self.read(4))[0] args = self._receive_rpc_values() logger.debug("rpc service: %d %r", rpc_num, args) - eid, r = rpc_wrapper.run_rpc( - user_exception_map, rpc_map[rpc_num], args) + eid, r = rpc_wrapper.run_rpc(rpc_map[rpc_num], args) self._write_header(9+2*4, _H2DMsgType.RPC_REPLY) self.write(struct.pack(">ll", eid, r)) logger.debug("rpc service: %d %r == %r (eid %d)", rpc_num, args, r, eid) - def _serve_exception(self, rpc_wrapper, user_exception_map): + def _serve_exception(self): eid, p0, p1, p2 = struct.unpack(">lqqq", self.read(4+3*8)) rpc_wrapper.filter_rpc_exception(eid) - if eid < core_language.first_user_eid: - exception = runtime_exceptions.exception_map[eid] - raise exception(self.core, p0, p1, p2) - else: - exception = user_exception_map[eid] - raise exception + raise exception(self.core, p0, p1, p2) - def serve(self, rpc_map, user_exception_map): - rpc_wrapper = RPCWrapper() + def serve(self, rpc_map): while True: _, ty = self._read_header() if ty == _D2HMsgType.RPC_REQUEST: - self._serve_rpc(rpc_wrapper, rpc_map, user_exception_map) + self._serve_rpc(rpc_map) elif ty == _D2HMsgType.KERNEL_EXCEPTION: - self._serve_exception(rpc_wrapper, user_exception_map) + self._serve_exception() elif ty == _D2HMsgType.KERNEL_FINISHED: return else: diff --git a/artiq/coredevice/core.py b/artiq/coredevice/core.py index 536a10054..7367f0529 100644 --- a/artiq/coredevice/core.py +++ b/artiq/coredevice/core.py @@ -1,50 +1,20 @@ -import os +import os, sys, tempfile + +from pythonparser import diagnostic from artiq.language.core import * from artiq.language.units import ns -from artiq.transforms.inline import inline -from artiq.transforms.quantize_time import quantize_time -from artiq.transforms.remove_inter_assigns import remove_inter_assigns -from artiq.transforms.fold_constants import fold_constants -from artiq.transforms.remove_dead_code import remove_dead_code -from artiq.transforms.unroll_loops import unroll_loops -from artiq.transforms.interleave import interleave -from artiq.transforms.lower_time import lower_time -from artiq.transforms.unparse import unparse +from artiq.compiler import Stitcher, Module +from artiq.compiler.targets import OR1KTarget -from artiq.coredevice.runtime import Runtime - -from artiq.py2llvm import get_runtime_binary +# Import for side effects (creating the exception classes). +from artiq.coredevice import exceptions -def _announce_unparse(label, node): - print("*** Unparsing: "+label) - print(unparse(node)) - - -def _make_debug_unparse(final): - try: - env = os.environ["ARTIQ_UNPARSE"] - except KeyError: - env = "" - selected_labels = set(env.split()) - if "all" in selected_labels: - return _announce_unparse - else: - if "final" in selected_labels: - selected_labels.add(final) - - def _filtered_unparse(label, node): - if label in selected_labels: - _announce_unparse(label, node) - return _filtered_unparse - - -def _no_debug_unparse(label, node): +class CompileError(Exception): pass - class Core: def __init__(self, dmgr, ref_period=8*ns, external_clock=False): self.comm = dmgr.get("comm") @@ -54,69 +24,40 @@ class Core: self.first_run = True self.core = self self.comm.core = self - self.runtime = Runtime() - def transform_stack(self, func_def, rpc_map, exception_map, - debug_unparse=_no_debug_unparse): - remove_inter_assigns(func_def) - debug_unparse("remove_inter_assigns_1", func_def) + def compile(self, function, args, kwargs, with_attr_writeback=True): + try: + engine = diagnostic.Engine(all_errors_are_fatal=True) - quantize_time(func_def, self.ref_period) - debug_unparse("quantize_time", func_def) + stitcher = Stitcher(engine=engine) + stitcher.stitch_call(function, args, kwargs) - fold_constants(func_def) - debug_unparse("fold_constants_1", func_def) + module = Module(stitcher) + library = OR1KTarget().compile_and_link([module]) - unroll_loops(func_def, 500) - debug_unparse("unroll_loops", func_def) + return library, stitcher.rpc_map + except diagnostic.Error as error: + print("\n".join(error.diagnostic.render(colored=True)), file=sys.stderr) + raise CompileError() from error - interleave(func_def) - debug_unparse("interleave", func_def) - - lower_time(func_def) - debug_unparse("lower_time", func_def) - - remove_inter_assigns(func_def) - debug_unparse("remove_inter_assigns_2", func_def) - - fold_constants(func_def) - debug_unparse("fold_constants_2", func_def) - - remove_dead_code(func_def) - debug_unparse("remove_dead_code_1", func_def) - - remove_inter_assigns(func_def) - debug_unparse("remove_inter_assigns_3", func_def) - - fold_constants(func_def) - debug_unparse("fold_constants_3", func_def) - - remove_dead_code(func_def) - debug_unparse("remove_dead_code_2", func_def) - - def compile(self, k_function, k_args, k_kwargs, with_attr_writeback=True): - debug_unparse = _make_debug_unparse("remove_dead_code_2") - - func_def, rpc_map, exception_map = inline( - self, k_function, k_args, k_kwargs, with_attr_writeback) - debug_unparse("inline", func_def) - self.transform_stack(func_def, rpc_map, exception_map, debug_unparse) - - binary = get_runtime_binary(self.runtime, func_def) - - return binary, rpc_map, exception_map - - def run(self, k_function, k_args, k_kwargs): + def run(self, function, args, kwargs): if self.first_run: self.comm.check_ident() self.comm.switch_clock(self.external_clock) + self.first_run = False - binary, rpc_map, exception_map = self.compile( - k_function, k_args, k_kwargs) - self.comm.load(binary) - self.comm.run(k_function.__name__) - self.comm.serve(rpc_map, exception_map) - self.first_run = False + kernel_library, rpc_map = self.compile(function, args, kwargs) + + try: + self.comm.load(kernel_library) + except Exception as error: + shlib_temp = tempfile.NamedTemporaryFile(suffix=".so", delete=False) + shlib_temp.write(kernel_library) + shlib_temp.close() + raise RuntimeError("shared library dumped to {}".format(shlib_temp.name)) from error + + self.comm.run() + self.comm.serve(rpc_map) @kernel def get_rtio_counter_mu(self): diff --git a/artiq/coredevice/exceptions.py b/artiq/coredevice/exceptions.py new file mode 100644 index 000000000..05d475908 --- /dev/null +++ b/artiq/coredevice/exceptions.py @@ -0,0 +1,33 @@ +from artiq.language.core import ARTIQException + + +class InternalError(ARTIQException): + """Raised when the runtime encounters an internal error condition.""" + +class RTIOUnderflow(ARTIQException): + """Raised when the CPU fails to submit a RTIO event early enough + (with respect to the event's timestamp). + + The offending event is discarded and the RTIO core keeps operating. + """ + +class RTIOSequenceError(ARTIQException): + """Raised when an event is submitted on a given channel with a timestamp + not larger than the previous one. + + The offending event is discarded and the RTIO core keeps operating. + """ + +class RTIOOverflow(ARTIQException): + """Raised when at least one event could not be registered into the RTIO + input FIFO because it was full (CPU not reading fast enough). + + This does not interrupt operations further than cancelling the current + read attempt and discarding some events. Reading can be reattempted after + the exception is caught, and events will be partially retrieved. + """ + +class DDSBatchError(ARTIQException): + """Raised when attempting to start a DDS batch while already in a batch, + or when too many commands are batched. + """ diff --git a/artiq/coredevice/rpc_wrapper.py b/artiq/coredevice/rpc_wrapper.py deleted file mode 100644 index eeae17286..000000000 --- a/artiq/coredevice/rpc_wrapper.py +++ /dev/null @@ -1,40 +0,0 @@ -from artiq.coredevice.runtime_exceptions import exception_map, _RPCException - - -def _lookup_exception(d, e): - for eid, exception in d.items(): - if isinstance(e, exception): - return eid - return 0 - - -class RPCWrapper: - def __init__(self): - self.last_exception = None - - def run_rpc(self, user_exception_map, fn, args): - eid = 0 - r = None - - try: - r = fn(*args) - except Exception as e: - eid = _lookup_exception(user_exception_map, e) - if not eid: - eid = _lookup_exception(exception_map, e) - if eid: - self.last_exception = None - else: - self.last_exception = e - eid = _RPCException.eid - - if r is None: - r = 0 - else: - r = int(r) - - return eid, r - - def filter_rpc_exception(self, eid): - if eid == _RPCException.eid: - raise self.last_exception diff --git a/artiq/coredevice/runtime.py b/artiq/coredevice/runtime.py index 84a6bb386..e716aecfc 100644 --- a/artiq/coredevice/runtime.py +++ b/artiq/coredevice/runtime.py @@ -3,14 +3,8 @@ import os import llvmlite_or1k.ir as ll import llvmlite_or1k.binding as llvm -from artiq.py2llvm import base_types, fractions, lists from artiq.language import units - -llvm.initialize() -llvm.initialize_all_targets() -llvm.initialize_all_asmprinters() - _syscalls = { "now_init": "n:I", "now_save": "I:n", @@ -97,27 +91,6 @@ class LinkInterface: self.syscalls[func_name] = ll.Function( llvm_module, func_type, "__syscall_" + func_name) - # exception handling - func_type = ll.FunctionType(ll.IntType(32), - [ll.PointerType(ll.IntType(8))]) - self.eh_setjmp = ll.Function(llvm_module, func_type, - "__eh_setjmp") - self.eh_setjmp.attributes.add("nounwind") - self.eh_setjmp.attributes.add("returns_twice") - - func_type = ll.FunctionType(ll.PointerType(ll.IntType(8)), []) - self.eh_push = ll.Function(llvm_module, func_type, "__eh_push") - - func_type = ll.FunctionType(ll.VoidType(), [ll.IntType(32)]) - self.eh_pop = ll.Function(llvm_module, func_type, "__eh_pop") - - func_type = ll.FunctionType(ll.IntType(32), []) - self.eh_getid = ll.Function(llvm_module, func_type, "__eh_getid") - - func_type = ll.FunctionType(ll.VoidType(), [ll.IntType(32)]) - self.eh_raise = ll.Function(llvm_module, func_type, "__eh_raise") - self.eh_raise.attributes.add("noreturn") - def _build_rpc(self, args, builder): r = base_types.VInt() if builder is not None: @@ -159,54 +132,3 @@ class LinkInterface: return self._build_rpc(args, builder) else: return self._build_regular_syscall(syscall_name, args, builder) - - def build_catch(self, builder): - jmpbuf = builder.call(self.eh_push, []) - exception_occured = builder.call(self.eh_setjmp, [jmpbuf]) - return builder.icmp_signed("!=", - exception_occured, - ll.Constant(ll.IntType(32), 0)) - - def build_pop(self, builder, levels): - builder.call(self.eh_pop, [ll.Constant(ll.IntType(32), levels)]) - - def build_getid(self, builder): - return builder.call(self.eh_getid, []) - - def build_raise(self, builder, eid): - builder.call(self.eh_raise, [eid]) - - -def _debug_dump_obj(obj): - try: - env = os.environ["ARTIQ_DUMP_OBJECT"] - except KeyError: - return - - for i in range(1000): - filename = "{}_{:03d}.elf".format(env, i) - try: - f = open(filename, "xb") - except FileExistsError: - pass - else: - f.write(obj) - f.close() - return - raise IOError - - -class Runtime(LinkInterface): - def __init__(self): - self.cpu_type = "or1k" - # allow 1ms for all initial DDS programming - self.warmup_time = 1*units.ms - - def emit_object(self): - tm = llvm.Target.from_triple(self.cpu_type).create_target_machine() - obj = tm.emit_object(self.module.llvm_module_ref) - _debug_dump_obj(obj) - return obj - - def __repr__(self): - return "".format(self.cpu_type) diff --git a/artiq/coredevice/runtime_exceptions.py b/artiq/coredevice/runtime_exceptions.py deleted file mode 100644 index aa9a2c1c5..000000000 --- a/artiq/coredevice/runtime_exceptions.py +++ /dev/null @@ -1,69 +0,0 @@ -import inspect - -from artiq.language.core import RuntimeException - - -# Must be kept in sync with soc/runtime/exceptions.h - -class InternalError(RuntimeException): - """Raised when the runtime encounters an internal error condition.""" - eid = 1 - - -class _RPCException(RuntimeException): - eid = 2 - - -class RTIOUnderflow(RuntimeException): - """Raised when the CPU fails to submit a RTIO event early enough - (with respect to the event's timestamp). - - The offending event is discarded and the RTIO core keeps operating. - """ - eid = 3 - - def __str__(self): - return "at {} on channel {}, violation {}".format( - self.p0*self.core.ref_period, - self.p1, - (self.p2 - self.p0)*self.core.ref_period) - - -class RTIOSequenceError(RuntimeException): - """Raised when an event is submitted on a given channel with a timestamp - not larger than the previous one. - - The offending event is discarded and the RTIO core keeps operating. - """ - eid = 4 - - def __str__(self): - return "at {} on channel {}".format(self.p0*self.core.ref_period, - self.p1) - - -class RTIOOverflow(RuntimeException): - """Raised when at least one event could not be registered into the RTIO - input FIFO because it was full (CPU not reading fast enough). - - This does not interrupt operations further than cancelling the current - read attempt and discarding some events. Reading can be reattempted after - the exception is caught, and events will be partially retrieved. - """ - eid = 5 - - def __str__(self): - return "on channel {}".format(self.p0) - - -class DDSBatchError(RuntimeException): - """Raised when attempting to start a DDS batch while already in a batch, - or when too many commands are batched. - """ - eid = 6 - - -exception_map = {e.eid: e for e in globals().values() - if inspect.isclass(e) - and issubclass(e, RuntimeException) - and hasattr(e, "eid")} diff --git a/artiq/language/core.py b/artiq/language/core.py index a4ca0b883..d8e24a552 100644 --- a/artiq/language/core.py +++ b/artiq/language/core.py @@ -8,7 +8,7 @@ from functools import wraps __all__ = ["int64", "round64", "kernel", "portable", "set_time_manager", "set_syscall_manager", "set_watchdog_factory", - "RuntimeException", "EncodedException"] + "ARTIQException"] # global namespace for kernels kernel_globals = ("sequential", "parallel", @@ -77,7 +77,7 @@ def round64(x): return int64(round(x)) -_KernelFunctionInfo = namedtuple("_KernelFunctionInfo", "core_name k_function") +_ARTIQEmbeddedInfo = namedtuple("_ARTIQEmbeddedInfo", "core_name function") def kernel(arg): @@ -100,25 +100,19 @@ def kernel(arg): specifies the name of the attribute to use as core device driver. """ if isinstance(arg, str): - def real_decorator(k_function): - @wraps(k_function) - def run_on_core(exp, *k_args, **k_kwargs): - return getattr(exp, arg).run(k_function, - ((exp,) + k_args), k_kwargs) - run_on_core.k_function_info = _KernelFunctionInfo( - core_name=arg, k_function=k_function) + def inner_decorator(function): + @wraps(function) + def run_on_core(self, *k_args, **k_kwargs): + return getattr(self, arg).run(function, ((self,) + k_args), k_kwargs) + run_on_core.artiq_embedded = _ARTIQEmbeddedInfo( + core_name=arg, function=function) return run_on_core - return real_decorator + return inner_decorator else: - @wraps(arg) - def run_on_core(exp, *k_args, **k_kwargs): - return exp.core.run(arg, ((exp,) + k_args), k_kwargs) - run_on_core.k_function_info = _KernelFunctionInfo( - core_name="core", k_function=arg) - return run_on_core + return kernel("core")(arg) -def portable(f): +def portable(function): """This decorator marks a function for execution on the same device as its caller. @@ -127,8 +121,8 @@ def portable(f): core device). A decorated function called from a kernel will be executed on the core device (no RPC). """ - f.k_function_info = _KernelFunctionInfo(core_name="", k_function=f) - return f + function.artiq_embedded = _ARTIQEmbeddedInfo(core_name="", function=function) + return function class _DummyTimeManager: @@ -280,32 +274,34 @@ def watchdog(timeout): return _watchdog_factory(timeout) -_encoded_exceptions = dict() +class ARTIQException(Exception): + """Base class for exceptions raised or passed through the core device.""" + # Try and create an instance of the specific class, if one exists. + def __new__(cls, name, message, params): + def find_subclass(cls): + if cls.__name__ == name: + return cls + else: + for subclass in cls.__subclasses__(): + cls = find_subclass(subclass) + if cls is not None: + return cls -def EncodedException(eid): - """Represents exceptions on the core device, which are identified - by a single number.""" - try: - return _encoded_exceptions[eid] - except KeyError: - class EncodedException(Exception): - def __init__(self): - Exception.__init__(self, eid) - _encoded_exceptions[eid] = EncodedException - return EncodedException + more_specific_cls = find_subclass(cls) + if more_specific_cls is None: + more_specific_cls = cls + exn = Exception.__new__(more_specific_cls) + exn.__init__(name, message, params) + return exn -class RuntimeException(Exception): - """Base class for all exceptions used by the device runtime. - Those exceptions are defined in ``artiq.coredevice.runtime_exceptions``. - """ - def __init__(self, core, p0, p1, p2): - Exception.__init__(self) - self.core = core - self.p0 = p0 - self.p1 = p1 - self.p2 = p2 + def __init__(self, name, message, params): + Exception.__init__(self, name, message, *params) + self.name, self.message, self.params = name, message, params - -first_user_eid = 1024 + def __str__(self): + if type(self).__name__ == self.name: + return self.message.format(*self.params) + else: + return "({}) {}".format(self.name, self.message.format(*self.params))