Skip to content

Commit 353f454

Browse files
author
whitequark
committedAug 7, 2015
Add basic support for embedded functions with new compiler.
1 parent b6e2613 commit 353f454

File tree

11 files changed

+284
-345
lines changed

11 files changed

+284
-345
lines changed
 

Diff for: ‎artiq/compiler/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
from .module import Module, Source
2+
from .embedding import Stitcher

Diff for: ‎artiq/compiler/asttyped.py

+2
Original file line numberDiff line numberDiff line change
@@ -93,3 +93,5 @@ class YieldFromT(ast.YieldFrom, commontyped):
9393
# Novel typed nodes
9494
class CoerceT(ast.expr, commontyped):
9595
_fields = ('value',) # other_value deliberately not in _fields
96+
class QuoteT(ast.expr, commontyped):
97+
_fields = ('value',)

Diff for: ‎artiq/compiler/embedding.py

+157
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
"""
2+
The :class:`Stitcher` class allows to transparently combine compiled
3+
Python code and Python code executed on the host system: it resolves
4+
the references to the host objects and translates the functions
5+
annotated as ``@kernel`` when they are referenced.
6+
"""
7+
8+
import inspect
9+
from pythonparser import ast, source, diagnostic, parse_buffer
10+
from . import types, builtins, asttyped, prelude
11+
from .transforms import ASTTypedRewriter, Inferencer
12+
13+
14+
class ASTSynthesizer:
15+
def __init__(self):
16+
self.source = ""
17+
self.source_buffer = source.Buffer(self.source, "<synthesized>")
18+
19+
def finalize(self):
20+
self.source_buffer.source = self.source
21+
return self.source_buffer
22+
23+
def _add(self, fragment):
24+
range_from = len(self.source)
25+
self.source += fragment
26+
range_to = len(self.source)
27+
return source.Range(self.source_buffer, range_from, range_to)
28+
29+
def quote(self, value):
30+
"""Construct an AST fragment equal to `value`."""
31+
if value in (None, True, False):
32+
if node.value is True or node.value is False:
33+
typ = builtins.TBool()
34+
elif node.value is None:
35+
typ = builtins.TNone()
36+
return asttyped.NameConstantT(value=value, type=typ,
37+
loc=self._add(repr(value)))
38+
elif isinstance(value, (int, float)):
39+
if isinstance(value, int):
40+
typ = builtins.TInt()
41+
elif isinstance(value, float):
42+
typ = builtins.TFloat()
43+
return asttyped.NumT(n=value, ctx=None, type=typ,
44+
loc=self._add(repr(value)))
45+
elif isinstance(value, list):
46+
begin_loc = self._add("[")
47+
elts = []
48+
for index, elt in value:
49+
elts.append(self.quote(elt))
50+
if index < len(value) - 1:
51+
self._add(", ")
52+
end_loc = self._add("]")
53+
return asttyped.ListT(elts=elts, ctx=None, type=types.TVar(),
54+
begin_loc=begin_loc, end_loc=end_loc,
55+
loc=begin_loc.join(end_loc))
56+
else:
57+
raise "no"
58+
# return asttyped.QuoteT(value=value, type=types.TVar())
59+
60+
def call(self, function_node, args, kwargs):
61+
"""
62+
Construct an AST fragment calling a function specified by
63+
an AST node `function_node`, with given arguments.
64+
"""
65+
arg_nodes = []
66+
kwarg_nodes = []
67+
kwarg_locs = []
68+
69+
name_loc = self._add(function_node.name)
70+
begin_loc = self._add("(")
71+
for index, arg in enumerate(args):
72+
arg_nodes.append(self.quote(arg))
73+
if index < len(args) - 1:
74+
self._add(", ")
75+
if any(args) and any(kwargs):
76+
self._add(", ")
77+
for index, kw in enumerate(kwargs):
78+
arg_loc = self._add(kw)
79+
equals_loc = self._add("=")
80+
kwarg_locs.append((arg_loc, equals_loc))
81+
kwarg_nodes.append(self.quote(kwargs[kw]))
82+
if index < len(kwargs) - 1:
83+
self._add(", ")
84+
end_loc = self._add(")")
85+
86+
return asttyped.CallT(
87+
func=asttyped.NameT(id=function_node.name, ctx=None,
88+
type=function_node.signature_type,
89+
loc=name_loc),
90+
args=arg_nodes,
91+
keywords=[ast.keyword(arg=kw, value=value,
92+
arg_loc=arg_loc, equals_loc=equals_loc,
93+
loc=arg_loc.join(value.loc))
94+
for kw, value, (arg_loc, equals_loc)
95+
in zip(kwargs, kwarg_nodes, kwarg_locs)],
96+
starargs=None, kwargs=None,
97+
type=types.TVar(),
98+
begin_loc=begin_loc, end_loc=end_loc, star_loc=None, dstar_loc=None,
99+
loc=name_loc.join(end_loc))
100+
101+
class StitchingASTTypedRewriter(ASTTypedRewriter):
102+
pass
103+
104+
class Stitcher:
105+
def __init__(self, engine=None):
106+
if engine is None:
107+
self.engine = diagnostic.Engine(all_errors_are_fatal=True)
108+
else:
109+
self.engine = engine
110+
111+
self.asttyped_rewriter = StitchingASTTypedRewriter(
112+
engine=self.engine, globals=prelude.globals())
113+
self.inferencer = Inferencer(engine=self.engine)
114+
115+
self.name = "stitched"
116+
self.typedtree = None
117+
self.globals = self.asttyped_rewriter.globals
118+
119+
self.rpc_map = {}
120+
121+
def _iterate(self):
122+
# Iterate inference to fixed point.
123+
self.inference_finished = False
124+
while not self.inference_finished:
125+
self.inference_finished = True
126+
self.inferencer.visit(self.typedtree)
127+
128+
def _parse_embedded_function(self, function):
129+
if not hasattr(function, "artiq_embedded"):
130+
raise ValueError("{} is not an embedded function".format(repr(function)))
131+
132+
# Extract function source.
133+
embedded_function = function.artiq_embedded.function
134+
source_code = inspect.getsource(embedded_function)
135+
filename = embedded_function.__code__.co_filename
136+
first_line = embedded_function.__code__.co_firstlineno
137+
138+
# Parse.
139+
source_buffer = source.Buffer(source_code, filename, first_line)
140+
parsetree, comments = parse_buffer(source_buffer, engine=self.engine)
141+
142+
# Rewrite into typed form.
143+
typedtree = self.asttyped_rewriter.visit(parsetree)
144+
145+
return typedtree, typedtree.body[0]
146+
147+
def stitch_call(self, function, args, kwargs):
148+
self.typedtree, function_node = self._parse_embedded_function(function)
149+
150+
# We synthesize fake source code for the initial call so that
151+
# diagnostics would have something meaningful to display to the user.
152+
synthesizer = ASTSynthesizer()
153+
call_node = synthesizer.call(function_node, args, kwargs)
154+
synthesizer.finalize()
155+
self.typedtree.body.append(call_node)
156+
157+
self._iterate()

Diff for: ‎artiq/compiler/transforms/asttyped_rewriter.py

+6-1
Original file line numberDiff line numberDiff line change
@@ -190,10 +190,15 @@ def __init__(self, engine, globals):
190190
self.globals = None
191191
self.env_stack = [globals]
192192

193-
def _find_name(self, name, loc):
193+
def _try_find_name(self, name):
194194
for typing_env in reversed(self.env_stack):
195195
if name in typing_env:
196196
return typing_env[name]
197+
198+
def _find_name(self, name, loc):
199+
typ = self._try_find_name(name)
200+
if typ is not None:
201+
return typ
197202
diag = diagnostic.Diagnostic("fatal",
198203
"name '{name}' is not bound to anything", {"name":name}, loc)
199204
self.engine.process(diag)

Diff for: ‎artiq/coredevice/comm_generic.py

+8-17
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,7 @@
33
from enum import Enum
44
from fractions import Fraction
55

6-
from artiq.coredevice import runtime_exceptions
76
from artiq.language import core as core_language
8-
from artiq.coredevice.rpc_wrapper import RPCWrapper
97

108

119
logger = logging.getLogger(__name__)
@@ -198,35 +196,28 @@ def _receive_rpc_values(self):
198196
else:
199197
r.append(self._receive_rpc_value(type_tag))
200198

201-
def _serve_rpc(self, rpc_wrapper, rpc_map, user_exception_map):
199+
def _serve_rpc(self, rpc_map):
202200
rpc_num = struct.unpack(">l", self.read(4))[0]
203201
args = self._receive_rpc_values()
204202
logger.debug("rpc service: %d %r", rpc_num, args)
205-
eid, r = rpc_wrapper.run_rpc(
206-
user_exception_map, rpc_map[rpc_num], args)
203+
eid, r = rpc_wrapper.run_rpc(rpc_map[rpc_num], args)
207204
self._write_header(9+2*4, _H2DMsgType.RPC_REPLY)
208205
self.write(struct.pack(">ll", eid, r))
209206
logger.debug("rpc service: %d %r == %r (eid %d)", rpc_num, args,
210207
r, eid)
211208

212-
def _serve_exception(self, rpc_wrapper, user_exception_map):
209+
def _serve_exception(self):
213210
eid, p0, p1, p2 = struct.unpack(">lqqq", self.read(4+3*8))
214211
rpc_wrapper.filter_rpc_exception(eid)
215-
if eid < core_language.first_user_eid:
216-
exception = runtime_exceptions.exception_map[eid]
217-
raise exception(self.core, p0, p1, p2)
218-
else:
219-
exception = user_exception_map[eid]
220-
raise exception
221-
222-
def serve(self, rpc_map, user_exception_map):
223-
rpc_wrapper = RPCWrapper()
212+
raise exception(self.core, p0, p1, p2)
213+
214+
def serve(self, rpc_map):
224215
while True:
225216
_, ty = self._read_header()
226217
if ty == _D2HMsgType.RPC_REQUEST:
227-
self._serve_rpc(rpc_wrapper, rpc_map, user_exception_map)
218+
self._serve_rpc(rpc_map)
228219
elif ty == _D2HMsgType.KERNEL_EXCEPTION:
229-
self._serve_exception(rpc_wrapper, user_exception_map)
220+
self._serve_exception()
230221
elif ty == _D2HMsgType.KERNEL_FINISHED:
231222
return
232223
else:

Diff for: ‎artiq/coredevice/core.py

+33-92
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,20 @@
1-
import os
1+
import os, sys, tempfile
2+
3+
from pythonparser import diagnostic
24

35
from artiq.language.core import *
46
from artiq.language.units import ns
57

6-
from artiq.transforms.inline import inline
7-
from artiq.transforms.quantize_time import quantize_time
8-
from artiq.transforms.remove_inter_assigns import remove_inter_assigns
9-
from artiq.transforms.fold_constants import fold_constants
10-
from artiq.transforms.remove_dead_code import remove_dead_code
11-
from artiq.transforms.unroll_loops import unroll_loops
12-
from artiq.transforms.interleave import interleave
13-
from artiq.transforms.lower_time import lower_time
14-
from artiq.transforms.unparse import unparse
15-
16-
from artiq.coredevice.runtime import Runtime
17-
18-
from artiq.py2llvm import get_runtime_binary
19-
20-
21-
def _announce_unparse(label, node):
22-
print("*** Unparsing: "+label)
23-
print(unparse(node))
24-
8+
from artiq.compiler import Stitcher, Module
9+
from artiq.compiler.targets import OR1KTarget
2510

26-
def _make_debug_unparse(final):
27-
try:
28-
env = os.environ["ARTIQ_UNPARSE"]
29-
except KeyError:
30-
env = ""
31-
selected_labels = set(env.split())
32-
if "all" in selected_labels:
33-
return _announce_unparse
34-
else:
35-
if "final" in selected_labels:
36-
selected_labels.add(final)
11+
# Import for side effects (creating the exception classes).
12+
from artiq.coredevice import exceptions
3713

38-
def _filtered_unparse(label, node):
39-
if label in selected_labels:
40-
_announce_unparse(label, node)
41-
return _filtered_unparse
4214

43-
44-
def _no_debug_unparse(label, node):
15+
class CompileError(Exception):
4516
pass
4617

47-
4818
class Core:
4919
def __init__(self, dmgr, ref_period=8*ns, external_clock=False):
5020
self.comm = dmgr.get("comm")
@@ -54,69 +24,40 @@ def __init__(self, dmgr, ref_period=8*ns, external_clock=False):
5424
self.first_run = True
5525
self.core = self
5626
self.comm.core = self
57-
self.runtime = Runtime()
58-
59-
def transform_stack(self, func_def, rpc_map, exception_map,
60-
debug_unparse=_no_debug_unparse):
61-
remove_inter_assigns(func_def)
62-
debug_unparse("remove_inter_assigns_1", func_def)
63-
64-
quantize_time(func_def, self.ref_period)
65-
debug_unparse("quantize_time", func_def)
66-
67-
fold_constants(func_def)
68-
debug_unparse("fold_constants_1", func_def)
6927

70-
unroll_loops(func_def, 500)
71-
debug_unparse("unroll_loops", func_def)
28+
def compile(self, function, args, kwargs, with_attr_writeback=True):
29+
try:
30+
engine = diagnostic.Engine(all_errors_are_fatal=True)
7231

73-
interleave(func_def)
74-
debug_unparse("interleave", func_def)
32+
stitcher = Stitcher(engine=engine)
33+
stitcher.stitch_call(function, args, kwargs)
7534

76-
lower_time(func_def)
77-
debug_unparse("lower_time", func_def)
35+
module = Module(stitcher)
36+
library = OR1KTarget().compile_and_link([module])
7837

79-
remove_inter_assigns(func_def)
80-
debug_unparse("remove_inter_assigns_2", func_def)
38+
return library, stitcher.rpc_map
39+
except diagnostic.Error as error:
40+
print("\n".join(error.diagnostic.render(colored=True)), file=sys.stderr)
41+
raise CompileError() from error
8142

82-
fold_constants(func_def)
83-
debug_unparse("fold_constants_2", func_def)
84-
85-
remove_dead_code(func_def)
86-
debug_unparse("remove_dead_code_1", func_def)
87-
88-
remove_inter_assigns(func_def)
89-
debug_unparse("remove_inter_assigns_3", func_def)
90-
91-
fold_constants(func_def)
92-
debug_unparse("fold_constants_3", func_def)
93-
94-
remove_dead_code(func_def)
95-
debug_unparse("remove_dead_code_2", func_def)
96-
97-
def compile(self, k_function, k_args, k_kwargs, with_attr_writeback=True):
98-
debug_unparse = _make_debug_unparse("remove_dead_code_2")
99-
100-
func_def, rpc_map, exception_map = inline(
101-
self, k_function, k_args, k_kwargs, with_attr_writeback)
102-
debug_unparse("inline", func_def)
103-
self.transform_stack(func_def, rpc_map, exception_map, debug_unparse)
104-
105-
binary = get_runtime_binary(self.runtime, func_def)
106-
107-
return binary, rpc_map, exception_map
108-
109-
def run(self, k_function, k_args, k_kwargs):
43+
def run(self, function, args, kwargs):
11044
if self.first_run:
11145
self.comm.check_ident()
11246
self.comm.switch_clock(self.external_clock)
47+
self.first_run = False
48+
49+
kernel_library, rpc_map = self.compile(function, args, kwargs)
50+
51+
try:
52+
self.comm.load(kernel_library)
53+
except Exception as error:
54+
shlib_temp = tempfile.NamedTemporaryFile(suffix=".so", delete=False)
55+
shlib_temp.write(kernel_library)
56+
shlib_temp.close()
57+
raise RuntimeError("shared library dumped to {}".format(shlib_temp.name)) from error
11358

114-
binary, rpc_map, exception_map = self.compile(
115-
k_function, k_args, k_kwargs)
116-
self.comm.load(binary)
117-
self.comm.run(k_function.__name__)
118-
self.comm.serve(rpc_map, exception_map)
119-
self.first_run = False
59+
self.comm.run()
60+
self.comm.serve(rpc_map)
12061

12162
@kernel
12263
def get_rtio_counter_mu(self):

Diff for: ‎artiq/coredevice/exceptions.py

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
from artiq.language.core import ARTIQException
2+
3+
4+
class InternalError(ARTIQException):
5+
"""Raised when the runtime encounters an internal error condition."""
6+
7+
class RTIOUnderflow(ARTIQException):
8+
"""Raised when the CPU fails to submit a RTIO event early enough
9+
(with respect to the event's timestamp).
10+
11+
The offending event is discarded and the RTIO core keeps operating.
12+
"""
13+
14+
class RTIOSequenceError(ARTIQException):
15+
"""Raised when an event is submitted on a given channel with a timestamp
16+
not larger than the previous one.
17+
18+
The offending event is discarded and the RTIO core keeps operating.
19+
"""
20+
21+
class RTIOOverflow(ARTIQException):
22+
"""Raised when at least one event could not be registered into the RTIO
23+
input FIFO because it was full (CPU not reading fast enough).
24+
25+
This does not interrupt operations further than cancelling the current
26+
read attempt and discarding some events. Reading can be reattempted after
27+
the exception is caught, and events will be partially retrieved.
28+
"""
29+
30+
class DDSBatchError(ARTIQException):
31+
"""Raised when attempting to start a DDS batch while already in a batch,
32+
or when too many commands are batched.
33+
"""

Diff for: ‎artiq/coredevice/rpc_wrapper.py

-40
This file was deleted.

Diff for: ‎artiq/coredevice/runtime.py

-78
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,8 @@
33
import llvmlite_or1k.ir as ll
44
import llvmlite_or1k.binding as llvm
55

6-
from artiq.py2llvm import base_types, fractions, lists
76
from artiq.language import units
87

9-
10-
llvm.initialize()
11-
llvm.initialize_all_targets()
12-
llvm.initialize_all_asmprinters()
13-
148
_syscalls = {
159
"now_init": "n:I",
1610
"now_save": "I:n",
@@ -97,27 +91,6 @@ def init_module(self, module):
9791
self.syscalls[func_name] = ll.Function(
9892
llvm_module, func_type, "__syscall_" + func_name)
9993

100-
# exception handling
101-
func_type = ll.FunctionType(ll.IntType(32),
102-
[ll.PointerType(ll.IntType(8))])
103-
self.eh_setjmp = ll.Function(llvm_module, func_type,
104-
"__eh_setjmp")
105-
self.eh_setjmp.attributes.add("nounwind")
106-
self.eh_setjmp.attributes.add("returns_twice")
107-
108-
func_type = ll.FunctionType(ll.PointerType(ll.IntType(8)), [])
109-
self.eh_push = ll.Function(llvm_module, func_type, "__eh_push")
110-
111-
func_type = ll.FunctionType(ll.VoidType(), [ll.IntType(32)])
112-
self.eh_pop = ll.Function(llvm_module, func_type, "__eh_pop")
113-
114-
func_type = ll.FunctionType(ll.IntType(32), [])
115-
self.eh_getid = ll.Function(llvm_module, func_type, "__eh_getid")
116-
117-
func_type = ll.FunctionType(ll.VoidType(), [ll.IntType(32)])
118-
self.eh_raise = ll.Function(llvm_module, func_type, "__eh_raise")
119-
self.eh_raise.attributes.add("noreturn")
120-
12194
def _build_rpc(self, args, builder):
12295
r = base_types.VInt()
12396
if builder is not None:
@@ -159,54 +132,3 @@ def build_syscall(self, syscall_name, args, builder):
159132
return self._build_rpc(args, builder)
160133
else:
161134
return self._build_regular_syscall(syscall_name, args, builder)
162-
163-
def build_catch(self, builder):
164-
jmpbuf = builder.call(self.eh_push, [])
165-
exception_occured = builder.call(self.eh_setjmp, [jmpbuf])
166-
return builder.icmp_signed("!=",
167-
exception_occured,
168-
ll.Constant(ll.IntType(32), 0))
169-
170-
def build_pop(self, builder, levels):
171-
builder.call(self.eh_pop, [ll.Constant(ll.IntType(32), levels)])
172-
173-
def build_getid(self, builder):
174-
return builder.call(self.eh_getid, [])
175-
176-
def build_raise(self, builder, eid):
177-
builder.call(self.eh_raise, [eid])
178-
179-
180-
def _debug_dump_obj(obj):
181-
try:
182-
env = os.environ["ARTIQ_DUMP_OBJECT"]
183-
except KeyError:
184-
return
185-
186-
for i in range(1000):
187-
filename = "{}_{:03d}.elf".format(env, i)
188-
try:
189-
f = open(filename, "xb")
190-
except FileExistsError:
191-
pass
192-
else:
193-
f.write(obj)
194-
f.close()
195-
return
196-
raise IOError
197-
198-
199-
class Runtime(LinkInterface):
200-
def __init__(self):
201-
self.cpu_type = "or1k"
202-
# allow 1ms for all initial DDS programming
203-
self.warmup_time = 1*units.ms
204-
205-
def emit_object(self):
206-
tm = llvm.Target.from_triple(self.cpu_type).create_target_machine()
207-
obj = tm.emit_object(self.module.llvm_module_ref)
208-
_debug_dump_obj(obj)
209-
return obj
210-
211-
def __repr__(self):
212-
return "<Runtime {}>".format(self.cpu_type)

Diff for: ‎artiq/coredevice/runtime_exceptions.py

-69
This file was deleted.

Diff for: ‎artiq/language/core.py

+44-48
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
__all__ = ["int64", "round64", "kernel", "portable",
1010
"set_time_manager", "set_syscall_manager", "set_watchdog_factory",
11-
"RuntimeException", "EncodedException"]
11+
"ARTIQException"]
1212

1313
# global namespace for kernels
1414
kernel_globals = ("sequential", "parallel",
@@ -77,7 +77,7 @@ def round64(x):
7777
return int64(round(x))
7878

7979

80-
_KernelFunctionInfo = namedtuple("_KernelFunctionInfo", "core_name k_function")
80+
_ARTIQEmbeddedInfo = namedtuple("_ARTIQEmbeddedInfo", "core_name function")
8181

8282

8383
def kernel(arg):
@@ -100,25 +100,19 @@ def kernel(arg):
100100
specifies the name of the attribute to use as core device driver.
101101
"""
102102
if isinstance(arg, str):
103-
def real_decorator(k_function):
104-
@wraps(k_function)
105-
def run_on_core(exp, *k_args, **k_kwargs):
106-
return getattr(exp, arg).run(k_function,
107-
((exp,) + k_args), k_kwargs)
108-
run_on_core.k_function_info = _KernelFunctionInfo(
109-
core_name=arg, k_function=k_function)
103+
def inner_decorator(function):
104+
@wraps(function)
105+
def run_on_core(self, *k_args, **k_kwargs):
106+
return getattr(self, arg).run(function, ((self,) + k_args), k_kwargs)
107+
run_on_core.artiq_embedded = _ARTIQEmbeddedInfo(
108+
core_name=arg, function=function)
110109
return run_on_core
111-
return real_decorator
110+
return inner_decorator
112111
else:
113-
@wraps(arg)
114-
def run_on_core(exp, *k_args, **k_kwargs):
115-
return exp.core.run(arg, ((exp,) + k_args), k_kwargs)
116-
run_on_core.k_function_info = _KernelFunctionInfo(
117-
core_name="core", k_function=arg)
118-
return run_on_core
112+
return kernel("core")(arg)
119113

120114

121-
def portable(f):
115+
def portable(function):
122116
"""This decorator marks a function for execution on the same device as its
123117
caller.
124118
@@ -127,8 +121,8 @@ def portable(f):
127121
core device). A decorated function called from a kernel will be executed
128122
on the core device (no RPC).
129123
"""
130-
f.k_function_info = _KernelFunctionInfo(core_name="", k_function=f)
131-
return f
124+
function.artiq_embedded = _ARTIQEmbeddedInfo(core_name="", function=function)
125+
return function
132126

133127

134128
class _DummyTimeManager:
@@ -280,32 +274,34 @@ def watchdog(timeout):
280274
return _watchdog_factory(timeout)
281275

282276

283-
_encoded_exceptions = dict()
284-
285-
286-
def EncodedException(eid):
287-
"""Represents exceptions on the core device, which are identified
288-
by a single number."""
289-
try:
290-
return _encoded_exceptions[eid]
291-
except KeyError:
292-
class EncodedException(Exception):
293-
def __init__(self):
294-
Exception.__init__(self, eid)
295-
_encoded_exceptions[eid] = EncodedException
296-
return EncodedException
297-
298-
299-
class RuntimeException(Exception):
300-
"""Base class for all exceptions used by the device runtime.
301-
Those exceptions are defined in ``artiq.coredevice.runtime_exceptions``.
302-
"""
303-
def __init__(self, core, p0, p1, p2):
304-
Exception.__init__(self)
305-
self.core = core
306-
self.p0 = p0
307-
self.p1 = p1
308-
self.p2 = p2
309-
310-
311-
first_user_eid = 1024
277+
class ARTIQException(Exception):
278+
"""Base class for exceptions raised or passed through the core device."""
279+
280+
# Try and create an instance of the specific class, if one exists.
281+
def __new__(cls, name, message, params):
282+
def find_subclass(cls):
283+
if cls.__name__ == name:
284+
return cls
285+
else:
286+
for subclass in cls.__subclasses__():
287+
cls = find_subclass(subclass)
288+
if cls is not None:
289+
return cls
290+
291+
more_specific_cls = find_subclass(cls)
292+
if more_specific_cls is None:
293+
more_specific_cls = cls
294+
295+
exn = Exception.__new__(more_specific_cls)
296+
exn.__init__(name, message, params)
297+
return exn
298+
299+
def __init__(self, name, message, params):
300+
Exception.__init__(self, name, message, *params)
301+
self.name, self.message, self.params = name, message, params
302+
303+
def __str__(self):
304+
if type(self).__name__ == self.name:
305+
return self.message.format(*self.params)
306+
else:
307+
return "({}) {}".format(self.name, self.message.format(*self.params))

0 commit comments

Comments
 (0)
Please sign in to comment.