forked from M-Labs/artiq
Merge branch 'master' into nac3
This commit is contained in:
commit
ceceabbaf0
|
@ -32,9 +32,6 @@ Highlights:
|
||||||
* Previously detected RTIO async errors are reported to the host after each kernel terminates and a
|
* Previously detected RTIO async errors are reported to the host after each kernel terminates and a
|
||||||
warning is logged. The warning is additional to the one already printed in the core device log upon
|
warning is logged. The warning is additional to the one already printed in the core device log upon
|
||||||
detection of the error.
|
detection of the error.
|
||||||
* HDF5 options can now be passed when creating datasets with ``set_dataset``. This allows
|
|
||||||
in particular to use transparent compression filters as follows:
|
|
||||||
``set_dataset(name, value, hdf5_options={"compression": "gzip"})``.
|
|
||||||
* Removed worker DB warning for writing a dataset that is also in the archive
|
* Removed worker DB warning for writing a dataset that is also in the archive
|
||||||
|
|
||||||
Breaking changes:
|
Breaking changes:
|
||||||
|
@ -51,10 +48,6 @@ Breaking changes:
|
||||||
calling `ADF5356.init()`.
|
calling `ADF5356.init()`.
|
||||||
* DRTIO: Changed message alignment from 32-bits to 64-bits.
|
* DRTIO: Changed message alignment from 32-bits to 64-bits.
|
||||||
* The deprecated ``set_dataset(..., save=...)`` is no longer supported.
|
* The deprecated ``set_dataset(..., save=...)`` is no longer supported.
|
||||||
* The internal dataset representation was changed to support tracking HDF5 options like e.g.
|
|
||||||
a compression method. This requires changes to code reading the dataset persistence file
|
|
||||||
(``dataset_db.pyon``) and to custom applets.
|
|
||||||
|
|
||||||
|
|
||||||
ARTIQ-6
|
ARTIQ-6
|
||||||
-------
|
-------
|
||||||
|
@ -129,7 +122,6 @@ Breaking changes:
|
||||||
* Experiment classes with underscore-prefixed names are now ignored when ``artiq_client``
|
* Experiment classes with underscore-prefixed names are now ignored when ``artiq_client``
|
||||||
determines which experiment to submit (consistent with ``artiq_run``).
|
determines which experiment to submit (consistent with ``artiq_run``).
|
||||||
|
|
||||||
|
|
||||||
ARTIQ-5
|
ARTIQ-5
|
||||||
-------
|
-------
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,7 @@ class NumberWidget(QtWidgets.QLCDNumber):
|
||||||
|
|
||||||
def data_changed(self, data, mods):
|
def data_changed(self, data, mods):
|
||||||
try:
|
try:
|
||||||
n = float(data[self.dataset_name]["value"])
|
n = float(data[self.dataset_name][1])
|
||||||
except (KeyError, ValueError, TypeError):
|
except (KeyError, ValueError, TypeError):
|
||||||
n = "---"
|
n = "---"
|
||||||
self.display(n)
|
self.display(n)
|
||||||
|
|
|
@ -13,7 +13,7 @@ class Image(pyqtgraph.ImageView):
|
||||||
|
|
||||||
def data_changed(self, data, mods):
|
def data_changed(self, data, mods):
|
||||||
try:
|
try:
|
||||||
img = data[self.args.img]["value"]
|
img = data[self.args.img][1]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
return
|
return
|
||||||
self.setImage(img)
|
self.setImage(img)
|
||||||
|
|
|
@ -17,11 +17,11 @@ class HistogramPlot(pyqtgraph.PlotWidget):
|
||||||
|
|
||||||
def data_changed(self, data, mods, title):
|
def data_changed(self, data, mods, title):
|
||||||
try:
|
try:
|
||||||
y = data[self.args.y]["value"]
|
y = data[self.args.y][1]
|
||||||
if self.args.x is None:
|
if self.args.x is None:
|
||||||
x = None
|
x = None
|
||||||
else:
|
else:
|
||||||
x = data[self.args.x]["value"]
|
x = data[self.args.x][1]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
return
|
return
|
||||||
if x is None:
|
if x is None:
|
||||||
|
|
|
@ -6,7 +6,6 @@ from PyQt5.QtCore import QTimer
|
||||||
import pyqtgraph
|
import pyqtgraph
|
||||||
|
|
||||||
from artiq.applets.simple import TitleApplet
|
from artiq.applets.simple import TitleApplet
|
||||||
from artiq.master.databases import make_dataset as empty_dataset
|
|
||||||
|
|
||||||
|
|
||||||
class XYPlot(pyqtgraph.PlotWidget):
|
class XYPlot(pyqtgraph.PlotWidget):
|
||||||
|
@ -22,14 +21,14 @@ class XYPlot(pyqtgraph.PlotWidget):
|
||||||
|
|
||||||
def data_changed(self, data, mods, title):
|
def data_changed(self, data, mods, title):
|
||||||
try:
|
try:
|
||||||
y = data[self.args.y]["value"]
|
y = data[self.args.y][1]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
return
|
return
|
||||||
x = data.get(self.args.x, empty_dataset())["value"]
|
x = data.get(self.args.x, (False, None))[1]
|
||||||
if x is None:
|
if x is None:
|
||||||
x = np.arange(len(y))
|
x = np.arange(len(y))
|
||||||
error = data.get(self.args.error, empty_dataset())["value"]
|
error = data.get(self.args.error, (False, None))[1]
|
||||||
fit = data.get(self.args.fit, empty_dataset())["value"]
|
fit = data.get(self.args.fit, (False, None))[1]
|
||||||
|
|
||||||
if not len(y) or len(y) != len(x):
|
if not len(y) or len(y) != len(x):
|
||||||
self.mismatch['X values'] = True
|
self.mismatch['X values'] = True
|
||||||
|
|
|
@ -126,9 +126,9 @@ class XYHistPlot(QtWidgets.QSplitter):
|
||||||
|
|
||||||
def data_changed(self, data, mods):
|
def data_changed(self, data, mods):
|
||||||
try:
|
try:
|
||||||
xs = data[self.args.xs]["value"]
|
xs = data[self.args.xs][1]
|
||||||
histogram_bins = data[self.args.histogram_bins]["value"]
|
histogram_bins = data[self.args.histogram_bins][1]
|
||||||
histograms_counts = data[self.args.histograms_counts]["value"]
|
histograms_counts = data[self.args.histograms_counts][1]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
return
|
return
|
||||||
if len(xs) != histograms_counts.shape[0]:
|
if len(xs) != histograms_counts.shape[0]:
|
||||||
|
|
|
@ -10,7 +10,6 @@ from sipyco.sync_struct import Subscriber, process_mod
|
||||||
from sipyco import pyon
|
from sipyco import pyon
|
||||||
from sipyco.pipe_ipc import AsyncioChildComm
|
from sipyco.pipe_ipc import AsyncioChildComm
|
||||||
|
|
||||||
from artiq.master.databases import make_dataset as empty_dataset
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -252,7 +251,7 @@ class TitleApplet(SimpleApplet):
|
||||||
|
|
||||||
def emit_data_changed(self, data, mod_buffer):
|
def emit_data_changed(self, data, mod_buffer):
|
||||||
if self.args.title is not None:
|
if self.args.title is not None:
|
||||||
title_values = {k.replace(".", "/"): data.get(k, empty_dataset())["value"]
|
title_values = {k.replace(".", "/"): data.get(k, (False, None))[1]
|
||||||
for k in self.dataset_title}
|
for k in self.dataset_title}
|
||||||
try:
|
try:
|
||||||
title = self.args.title.format(**title_values)
|
title = self.args.title.format(**title_values)
|
||||||
|
|
|
@ -104,8 +104,8 @@ class DatasetsDock(QtWidgets.QDockWidget):
|
||||||
idx = self.table_model_filter.mapToSource(idx[0])
|
idx = self.table_model_filter.mapToSource(idx[0])
|
||||||
key = self.table_model.index_to_key(idx)
|
key = self.table_model.index_to_key(idx)
|
||||||
if key is not None:
|
if key is not None:
|
||||||
dataset = self.table_model.backing_store[key]
|
persist, value = self.table_model.backing_store[key]
|
||||||
asyncio.ensure_future(self._upload_dataset(key, dataset["value"]))
|
asyncio.ensure_future(self._upload_dataset(key, value))
|
||||||
|
|
||||||
def save_state(self):
|
def save_state(self):
|
||||||
return bytes(self.table.header().saveState())
|
return bytes(self.table.header().saveState())
|
||||||
|
|
|
@ -571,29 +571,33 @@ class CommKernel:
|
||||||
|
|
||||||
self._write_header(Request.RPCException)
|
self._write_header(Request.RPCException)
|
||||||
|
|
||||||
|
# Note: instead of sending strings, we send object ID
|
||||||
|
# This is to avoid the need of allocatio on the device side
|
||||||
|
# This is a special case: this only applies to exceptions
|
||||||
if hasattr(exn, "artiq_core_exception"):
|
if hasattr(exn, "artiq_core_exception"):
|
||||||
exn = exn.artiq_core_exception
|
exn = exn.artiq_core_exception
|
||||||
self._write_string(exn.name)
|
self._write_int32(embedding_map.store_str(exn.name))
|
||||||
self._write_string(self._truncate_message(exn.message))
|
self._write_int32(embedding_map.store_str(self._truncate_message(exn.message)))
|
||||||
for index in range(3):
|
for index in range(3):
|
||||||
self._write_int64(exn.param[index])
|
self._write_int64(exn.param[index])
|
||||||
|
|
||||||
filename, line, column, function = exn.traceback[-1]
|
filename, line, column, function = exn.traceback[-1]
|
||||||
self._write_string(filename)
|
self._write_int32(embedding_map.store_str(filename))
|
||||||
self._write_int32(line)
|
self._write_int32(line)
|
||||||
self._write_int32(column)
|
self._write_int32(column)
|
||||||
self._write_string(function)
|
self._write_int32(embedding_map.store_str(function))
|
||||||
else:
|
else:
|
||||||
exn_type = type(exn)
|
exn_type = type(exn)
|
||||||
if exn_type in (ZeroDivisionError, ValueError, IndexError, RuntimeError) or \
|
if exn_type in (ZeroDivisionError, ValueError, IndexError, RuntimeError) or \
|
||||||
hasattr(exn, "artiq_builtin"):
|
hasattr(exn, "artiq_builtin"):
|
||||||
self._write_string("0:{}".format(exn_type.__name__))
|
name = "0:{}".format(exn_type.__name__)
|
||||||
else:
|
else:
|
||||||
exn_id = embedding_map.store_object(exn_type)
|
exn_id = embedding_map.store_object(exn_type)
|
||||||
self._write_string("{}:{}.{}".format(exn_id,
|
name = "{}:{}.{}".format(exn_id,
|
||||||
exn_type.__module__,
|
exn_type.__module__,
|
||||||
exn_type.__qualname__))
|
exn_type.__qualname__)
|
||||||
self._write_string(self._truncate_message(str(exn)))
|
self._write_int32(embedding_map.store_str(name))
|
||||||
|
self._write_int32(embedding_map.store_str(self._truncate_message(str(exn))))
|
||||||
for index in range(3):
|
for index in range(3):
|
||||||
self._write_int64(0)
|
self._write_int64(0)
|
||||||
|
|
||||||
|
@ -604,10 +608,10 @@ class CommKernel:
|
||||||
((filename, line, function, _), ) = tb
|
((filename, line, function, _), ) = tb
|
||||||
else:
|
else:
|
||||||
assert False
|
assert False
|
||||||
self._write_string(filename)
|
self._write_int32(embedding_map.store_str(filename))
|
||||||
self._write_int32(line)
|
self._write_int32(line)
|
||||||
self._write_int32(-1) # column not known
|
self._write_int32(-1) # column not known
|
||||||
self._write_string(function)
|
self._write_int32(embedding_map.store_str(function))
|
||||||
self._flush()
|
self._flush()
|
||||||
else:
|
else:
|
||||||
logger.debug("rpc service: %d %r %r = %r",
|
logger.debug("rpc service: %d %r %r = %r",
|
||||||
|
@ -619,28 +623,59 @@ class CommKernel:
|
||||||
self._flush()
|
self._flush()
|
||||||
|
|
||||||
def _serve_exception(self, embedding_map, symbolizer, demangler):
|
def _serve_exception(self, embedding_map, symbolizer, demangler):
|
||||||
name = self._read_string()
|
exception_count = self._read_int32()
|
||||||
message = self._read_string()
|
nested_exceptions = []
|
||||||
|
|
||||||
|
def read_exception_string():
|
||||||
|
# note: if length == -1, the following int32 is the object key
|
||||||
|
length = self._read_int32()
|
||||||
|
if length == -1:
|
||||||
|
return embedding_map.retrieve_str(self._read_int32())
|
||||||
|
else:
|
||||||
|
return self._read(length).decode("utf-8")
|
||||||
|
|
||||||
|
for _ in range(exception_count):
|
||||||
|
name = embedding_map.retrieve_str(self._read_int32())
|
||||||
|
message = read_exception_string()
|
||||||
params = [self._read_int64() for _ in range(3)]
|
params = [self._read_int64() for _ in range(3)]
|
||||||
|
|
||||||
filename = self._read_string()
|
filename = read_exception_string()
|
||||||
line = self._read_int32()
|
line = self._read_int32()
|
||||||
column = self._read_int32()
|
column = self._read_int32()
|
||||||
function = self._read_string()
|
function = read_exception_string()
|
||||||
|
nested_exceptions.append([name, message, params,
|
||||||
|
filename, line, column, function])
|
||||||
|
|
||||||
|
demangled_names = demangler([ex[6] for ex in nested_exceptions])
|
||||||
|
for i in range(exception_count):
|
||||||
|
nested_exceptions[i][6] = demangled_names[i]
|
||||||
|
|
||||||
|
exception_info = []
|
||||||
|
for _ in range(exception_count):
|
||||||
|
sp = self._read_int32()
|
||||||
|
initial_backtrace = self._read_int32()
|
||||||
|
current_backtrace = self._read_int32()
|
||||||
|
exception_info.append((sp, initial_backtrace, current_backtrace))
|
||||||
|
|
||||||
|
backtrace = []
|
||||||
|
stack_pointers = []
|
||||||
|
for _ in range(self._read_int32()):
|
||||||
|
backtrace.append(self._read_int32())
|
||||||
|
stack_pointers.append(self._read_int32())
|
||||||
|
|
||||||
backtrace = [self._read_int32() for _ in range(self._read_int32())]
|
|
||||||
self._process_async_error()
|
self._process_async_error()
|
||||||
|
|
||||||
traceback = list(reversed(symbolizer(backtrace))) + \
|
traceback = list(symbolizer(backtrace))
|
||||||
[(filename, line, column, *demangler([function]), None)]
|
core_exn = exceptions.CoreException(nested_exceptions, exception_info,
|
||||||
core_exn = exceptions.CoreException(name, message, params, traceback)
|
traceback, stack_pointers)
|
||||||
|
|
||||||
if core_exn.id == 0:
|
if core_exn.id == 0:
|
||||||
python_exn_type = getattr(exceptions, core_exn.name.split('.')[-1])
|
python_exn_type = getattr(exceptions, core_exn.name.split('.')[-1])
|
||||||
else:
|
else:
|
||||||
python_exn_type = embedding_map.retrieve_object(core_exn.id)
|
python_exn_type = embedding_map.retrieve_object(core_exn.id)
|
||||||
|
|
||||||
python_exn = python_exn_type(message.format(*params))
|
python_exn = python_exn_type(
|
||||||
|
nested_exceptions[-1][1].format(*nested_exceptions[0][2]))
|
||||||
python_exn.artiq_core_exception = core_exn
|
python_exn.artiq_core_exception = core_exn
|
||||||
raise python_exn
|
raise python_exn
|
||||||
|
|
||||||
|
|
|
@ -16,50 +16,94 @@ AssertionError = builtins.AssertionError
|
||||||
|
|
||||||
class CoreException:
|
class CoreException:
|
||||||
"""Information about an exception raised or passed through the core device."""
|
"""Information about an exception raised or passed through the core device."""
|
||||||
|
def __init__(self, exceptions, exception_info, traceback, stack_pointers):
|
||||||
|
self.exceptions = exceptions
|
||||||
|
self.exception_info = exception_info
|
||||||
|
self.traceback = list(traceback)
|
||||||
|
self.stack_pointers = stack_pointers
|
||||||
|
|
||||||
def __init__(self, name, message, params, traceback):
|
first_exception = exceptions[0]
|
||||||
|
name = first_exception[0]
|
||||||
if ':' in name:
|
if ':' in name:
|
||||||
exn_id, self.name = name.split(':', 2)
|
exn_id, self.name = name.split(':', 2)
|
||||||
self.id = int(exn_id)
|
self.id = int(exn_id)
|
||||||
else:
|
else:
|
||||||
self.id, self.name = 0, name
|
self.id, self.name = 0, name
|
||||||
self.message, self.params = message, params
|
self.message = first_exception[1]
|
||||||
self.traceback = list(traceback)
|
self.params = first_exception[2]
|
||||||
|
|
||||||
def __str__(self):
|
def append_backtrace(self, record, inlined=False):
|
||||||
lines = []
|
filename, line, column, function, address = record
|
||||||
lines.append("Core Device Traceback (most recent call last):")
|
|
||||||
last_address = 0
|
|
||||||
for (filename, line, column, function, address) in self.traceback:
|
|
||||||
stub_globals = {"__name__": filename, "__loader__": source_loader}
|
stub_globals = {"__name__": filename, "__loader__": source_loader}
|
||||||
source_line = linecache.getline(filename, line, stub_globals)
|
source_line = linecache.getline(filename, line, stub_globals)
|
||||||
indentation = re.search(r"^\s*", source_line).end()
|
indentation = re.search(r"^\s*", source_line).end()
|
||||||
|
|
||||||
if address is None:
|
if address is None:
|
||||||
formatted_address = ""
|
formatted_address = ""
|
||||||
elif address == last_address:
|
elif inlined:
|
||||||
formatted_address = " (inlined)"
|
formatted_address = " (inlined)"
|
||||||
else:
|
else:
|
||||||
formatted_address = " (RA=+0x{:x})".format(address)
|
formatted_address = " (RA=+0x{:x})".format(address)
|
||||||
last_address = address
|
|
||||||
|
|
||||||
filename = filename.replace(artiq_dir, "<artiq>")
|
filename = filename.replace(artiq_dir, "<artiq>")
|
||||||
|
lines = []
|
||||||
if column == -1:
|
if column == -1:
|
||||||
|
lines.append(" {}".format(source_line.strip() if source_line else "<unknown>"))
|
||||||
lines.append(" File \"{file}\", line {line}, in {function}{address}".
|
lines.append(" File \"{file}\", line {line}, in {function}{address}".
|
||||||
format(file=filename, line=line, function=function,
|
format(file=filename, line=line, function=function,
|
||||||
address=formatted_address))
|
address=formatted_address))
|
||||||
lines.append(" {}".format(source_line.strip() if source_line else "<unknown>"))
|
|
||||||
else:
|
else:
|
||||||
|
lines.append(" {}^".format(" " * (column - indentation)))
|
||||||
|
lines.append(" {}".format(source_line.strip() if source_line else "<unknown>"))
|
||||||
lines.append(" File \"{file}\", line {line}, column {column},"
|
lines.append(" File \"{file}\", line {line}, column {column},"
|
||||||
" in {function}{address}".
|
" in {function}{address}".
|
||||||
format(file=filename, line=line, column=column + 1,
|
format(file=filename, line=line, column=column + 1,
|
||||||
function=function, address=formatted_address))
|
function=function, address=formatted_address))
|
||||||
lines.append(" {}".format(source_line.strip() if source_line else "<unknown>"))
|
return lines
|
||||||
lines.append(" {}^".format(" " * (column - indentation)))
|
|
||||||
|
|
||||||
lines.append("{}({}): {}".format(self.name, self.id,
|
def single_traceback(self, exception_index):
|
||||||
self.message.format(*self.params)))
|
# note that we insert in reversed order
|
||||||
return "\n".join(lines)
|
lines = []
|
||||||
|
last_sp = 0
|
||||||
|
start_backtrace_index = self.exception_info[exception_index][1]
|
||||||
|
zipped = list(zip(self.traceback[start_backtrace_index:],
|
||||||
|
self.stack_pointers[start_backtrace_index:]))
|
||||||
|
exception = self.exceptions[exception_index]
|
||||||
|
name = exception[0]
|
||||||
|
message = exception[1]
|
||||||
|
params = exception[2]
|
||||||
|
if ':' in name:
|
||||||
|
exn_id, name = name.split(':', 2)
|
||||||
|
exn_id = int(exn_id)
|
||||||
|
else:
|
||||||
|
exn_id = 0
|
||||||
|
lines.append("{}({}): {}".format(name, exn_id, message.format(*params)))
|
||||||
|
zipped.append(((exception[3], exception[4], exception[5], exception[6],
|
||||||
|
None, []), None))
|
||||||
|
|
||||||
|
for ((filename, line, column, function, address, inlined), sp) in zipped:
|
||||||
|
# backtrace of nested exceptions may be discontinuous
|
||||||
|
# but the stack pointer must increase monotonically
|
||||||
|
if sp is not None and sp <= last_sp:
|
||||||
|
continue
|
||||||
|
last_sp = sp
|
||||||
|
|
||||||
|
for record in reversed(inlined):
|
||||||
|
lines += self.append_backtrace(record, True)
|
||||||
|
lines += self.append_backtrace((filename, line, column, function,
|
||||||
|
address))
|
||||||
|
|
||||||
|
lines.append("Traceback (most recent call first):")
|
||||||
|
|
||||||
|
return "\n".join(reversed(lines))
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
tracebacks = [self.single_traceback(i) for i in range(len(self.exceptions))]
|
||||||
|
traceback_str = ('\n\nDuring handling of the above exception, ' +
|
||||||
|
'another exception occurred:\n\n').join(tracebacks)
|
||||||
|
return 'Core Device Traceback:\n' +\
|
||||||
|
traceback_str +\
|
||||||
|
'\n\nEnd of Code Device Traceback\n'
|
||||||
|
|
||||||
|
|
||||||
class InternalError(Exception):
|
class InternalError(Exception):
|
||||||
|
|
|
@ -147,15 +147,15 @@ class Creator(QtWidgets.QDialog):
|
||||||
|
|
||||||
class Model(DictSyncTreeSepModel):
|
class Model(DictSyncTreeSepModel):
|
||||||
def __init__(self, init):
|
def __init__(self, init):
|
||||||
DictSyncTreeSepModel.__init__(
|
DictSyncTreeSepModel.__init__(self, ".",
|
||||||
self, ".", ["Dataset", "Persistent", "Value"], init
|
["Dataset", "Persistent", "Value"],
|
||||||
)
|
init)
|
||||||
|
|
||||||
def convert(self, k, v, column):
|
def convert(self, k, v, column):
|
||||||
if column == 1:
|
if column == 1:
|
||||||
return "Y" if v["persist"] else "N"
|
return "Y" if v[0] else "N"
|
||||||
elif column == 2:
|
elif column == 2:
|
||||||
return short_format(v["value"])
|
return short_format(v[1])
|
||||||
else:
|
else:
|
||||||
raise ValueError
|
raise ValueError
|
||||||
|
|
||||||
|
@ -223,8 +223,8 @@ class DatasetsDock(QtWidgets.QDockWidget):
|
||||||
idx = self.table_model_filter.mapToSource(idx[0])
|
idx = self.table_model_filter.mapToSource(idx[0])
|
||||||
key = self.table_model.index_to_key(idx)
|
key = self.table_model.index_to_key(idx)
|
||||||
if key is not None:
|
if key is not None:
|
||||||
dataset = self.table_model.backing_store[key]
|
persist, value = self.table_model.backing_store[key]
|
||||||
t = type(dataset["value"])
|
t = type(value)
|
||||||
if np.issubdtype(t, np.number):
|
if np.issubdtype(t, np.number):
|
||||||
dialog_cls = NumberEditor
|
dialog_cls = NumberEditor
|
||||||
elif np.issubdtype(t, np.bool_):
|
elif np.issubdtype(t, np.bool_):
|
||||||
|
@ -235,7 +235,7 @@ class DatasetsDock(QtWidgets.QDockWidget):
|
||||||
logger.error("Cannot edit dataset %s: "
|
logger.error("Cannot edit dataset %s: "
|
||||||
"type %s is not supported", key, t)
|
"type %s is not supported", key, t)
|
||||||
return
|
return
|
||||||
dialog_cls(self, self.dataset_ctl, key, dataset["value"]).open()
|
dialog_cls(self, self.dataset_ctl, key, value).open()
|
||||||
|
|
||||||
def delete_clicked(self):
|
def delete_clicked(self):
|
||||||
idx = self.table.selectedIndexes()
|
idx = self.table.selectedIndexes()
|
||||||
|
|
|
@ -253,6 +253,7 @@ dependencies = [
|
||||||
"byteorder",
|
"byteorder",
|
||||||
"cslice",
|
"cslice",
|
||||||
"dyld",
|
"dyld",
|
||||||
|
"eh",
|
||||||
"failure",
|
"failure",
|
||||||
"failure_derive",
|
"failure_derive",
|
||||||
"io",
|
"io",
|
||||||
|
|
|
@ -117,7 +117,8 @@ static mut API: &'static [(&'static str, *const ())] = &[
|
||||||
api!(_Unwind_Resume = ::unwind::_Unwind_Resume),
|
api!(_Unwind_Resume = ::unwind::_Unwind_Resume),
|
||||||
api!(__artiq_personality = ::eh_artiq::personality),
|
api!(__artiq_personality = ::eh_artiq::personality),
|
||||||
api!(__artiq_raise = ::eh_artiq::raise),
|
api!(__artiq_raise = ::eh_artiq::raise),
|
||||||
api!(__artiq_reraise = ::eh_artiq::reraise),
|
api!(__artiq_resume = ::eh_artiq::resume),
|
||||||
|
api!(__artiq_end_catch = ::eh_artiq::end_catch),
|
||||||
|
|
||||||
/* proxified syscalls */
|
/* proxified syscalls */
|
||||||
api!(core_log),
|
api!(core_log),
|
||||||
|
|
|
@ -10,12 +10,15 @@
|
||||||
// except according to those terms.
|
// except according to those terms.
|
||||||
#![allow(non_camel_case_types)]
|
#![allow(non_camel_case_types)]
|
||||||
|
|
||||||
use core::{ptr, mem};
|
use core::mem;
|
||||||
use cslice::CSlice;
|
use cslice::AsCSlice;
|
||||||
use unwind as uw;
|
use unwind as uw;
|
||||||
use libc::{c_int, c_void};
|
use libc::{c_int, c_void};
|
||||||
|
|
||||||
use eh::dwarf::{self, EHAction, EHContext};
|
use eh::{self, dwarf::{self, EHAction, EHContext}};
|
||||||
|
|
||||||
|
pub type Exception<'a> = eh::eh_artiq::Exception<'a>;
|
||||||
|
pub type StackPointerBacktrace = eh::eh_artiq::StackPointerBacktrace;
|
||||||
|
|
||||||
type _Unwind_Stop_Fn = extern "C" fn(version: c_int,
|
type _Unwind_Stop_Fn = extern "C" fn(version: c_int,
|
||||||
actions: uw::_Unwind_Action,
|
actions: uw::_Unwind_Action,
|
||||||
|
@ -30,40 +33,74 @@ extern {
|
||||||
stop_parameter: *mut c_void) -> uw::_Unwind_Reason_Code;
|
stop_parameter: *mut c_void) -> uw::_Unwind_Reason_Code;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[repr(C)]
|
pub static mut PAYLOAD_ADDRESS: usize = 0;
|
||||||
#[derive(Clone, Copy)]
|
|
||||||
pub struct Exception<'a> {
|
|
||||||
pub name: CSlice<'a, u8>,
|
|
||||||
pub file: CSlice<'a, u8>,
|
|
||||||
pub line: u32,
|
|
||||||
pub column: u32,
|
|
||||||
pub function: CSlice<'a, u8>,
|
|
||||||
pub message: CSlice<'a, u8>,
|
|
||||||
pub param: [i64; 3]
|
|
||||||
}
|
|
||||||
|
|
||||||
const EXCEPTION_CLASS: uw::_Unwind_Exception_Class = 0x4d_4c_42_53_41_52_54_51; /* 'MLBSARTQ' */
|
const EXCEPTION_CLASS: uw::_Unwind_Exception_Class = 0x4d_4c_42_53_41_52_54_51; /* 'MLBSARTQ' */
|
||||||
|
|
||||||
|
const MAX_INFLIGHT_EXCEPTIONS: usize = 10;
|
||||||
const MAX_BACKTRACE_SIZE: usize = 128;
|
const MAX_BACKTRACE_SIZE: usize = 128;
|
||||||
|
|
||||||
#[repr(C)]
|
struct ExceptionBuffer {
|
||||||
struct ExceptionInfo {
|
// we need n _Unwind_Exception, because each will have their own private data
|
||||||
uw_exception: uw::_Unwind_Exception,
|
uw_exceptions: [uw::_Unwind_Exception; MAX_INFLIGHT_EXCEPTIONS],
|
||||||
exception: Option<Exception<'static>>,
|
exceptions: [Option<Exception<'static>>; MAX_INFLIGHT_EXCEPTIONS + 1],
|
||||||
handled: bool,
|
exception_stack: [isize; MAX_INFLIGHT_EXCEPTIONS + 1],
|
||||||
backtrace: [usize; MAX_BACKTRACE_SIZE],
|
// nested exceptions will share the backtrace buffer, treated as a tree
|
||||||
backtrace_size: usize
|
// backtrace contains a tuple of IP and SP
|
||||||
|
backtrace: [(usize, usize); MAX_BACKTRACE_SIZE],
|
||||||
|
backtrace_size: usize,
|
||||||
|
// stack pointers are stored to reconstruct backtrace for each exception
|
||||||
|
stack_pointers: [StackPointerBacktrace; MAX_INFLIGHT_EXCEPTIONS + 1],
|
||||||
|
// current allocated nested exceptions
|
||||||
|
exception_count: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
static mut EXCEPTION_BUFFER: ExceptionBuffer = ExceptionBuffer {
|
||||||
|
uw_exceptions: [uw::_Unwind_Exception {
|
||||||
|
exception_class: EXCEPTION_CLASS,
|
||||||
|
exception_cleanup: cleanup,
|
||||||
|
private: [0; uw::unwinder_private_data_size],
|
||||||
|
}; MAX_INFLIGHT_EXCEPTIONS],
|
||||||
|
exceptions: [None; MAX_INFLIGHT_EXCEPTIONS + 1],
|
||||||
|
exception_stack: [-1; MAX_INFLIGHT_EXCEPTIONS + 1],
|
||||||
|
backtrace: [(0, 0); MAX_BACKTRACE_SIZE],
|
||||||
|
backtrace_size: 0,
|
||||||
|
stack_pointers: [StackPointerBacktrace {
|
||||||
|
stack_pointer: 0,
|
||||||
|
initial_backtrace_size: 0,
|
||||||
|
current_backtrace_size: 0
|
||||||
|
}; MAX_INFLIGHT_EXCEPTIONS + 1],
|
||||||
|
exception_count: 0
|
||||||
|
};
|
||||||
|
|
||||||
|
pub unsafe extern fn reset_exception_buffer(payload_addr: usize) {
|
||||||
|
EXCEPTION_BUFFER.uw_exceptions = [uw::_Unwind_Exception {
|
||||||
|
exception_class: EXCEPTION_CLASS,
|
||||||
|
exception_cleanup: cleanup,
|
||||||
|
private: [0; uw::unwinder_private_data_size],
|
||||||
|
}; MAX_INFLIGHT_EXCEPTIONS];
|
||||||
|
EXCEPTION_BUFFER.exceptions = [None; MAX_INFLIGHT_EXCEPTIONS + 1];
|
||||||
|
EXCEPTION_BUFFER.exception_stack = [-1; MAX_INFLIGHT_EXCEPTIONS + 1];
|
||||||
|
EXCEPTION_BUFFER.backtrace_size = 0;
|
||||||
|
EXCEPTION_BUFFER.exception_count = 0;
|
||||||
|
PAYLOAD_ADDRESS = payload_addr;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(target_arch = "x86_64")]
|
#[cfg(target_arch = "x86_64")]
|
||||||
const UNWIND_DATA_REG: (i32, i32) = (0, 1); // RAX, RDX
|
const UNWIND_DATA_REG: (i32, i32) = (0, 1); // RAX, RDX
|
||||||
|
#[cfg(target_arch = "x86_64")]
|
||||||
|
// actually this is not the SP, but frame pointer
|
||||||
|
// but it serves its purpose, and getting SP will somehow cause segfault...
|
||||||
|
const UNW_FP_REG: c_int = 12;
|
||||||
|
|
||||||
#[cfg(any(target_arch = "riscv32"))]
|
#[cfg(any(target_arch = "riscv32"))]
|
||||||
const UNWIND_DATA_REG: (i32, i32) = (10, 11); // X10, X11
|
const UNWIND_DATA_REG: (i32, i32) = (10, 11); // X10, X11
|
||||||
|
#[cfg(any(target_arch = "riscv32"))]
|
||||||
|
const UNW_FP_REG: c_int = 2;
|
||||||
|
|
||||||
#[export_name="__artiq_personality"]
|
#[export_name="__artiq_personality"]
|
||||||
pub extern fn personality(version: c_int,
|
pub extern fn personality(version: c_int,
|
||||||
actions: uw::_Unwind_Action,
|
_actions: uw::_Unwind_Action,
|
||||||
uw_exception_class: uw::_Unwind_Exception_Class,
|
uw_exception_class: uw::_Unwind_Exception_Class,
|
||||||
uw_exception: *mut uw::_Unwind_Exception,
|
uw_exception: *mut uw::_Unwind_Exception,
|
||||||
context: *mut uw::_Unwind_Context)
|
context: *mut uw::_Unwind_Context)
|
||||||
|
@ -85,32 +122,20 @@ pub extern fn personality(version: c_int,
|
||||||
get_data_start: &|| uw::_Unwind_GetDataRelBase(context),
|
get_data_start: &|| uw::_Unwind_GetDataRelBase(context),
|
||||||
};
|
};
|
||||||
|
|
||||||
let exception_info = &mut *(uw_exception as *mut ExceptionInfo);
|
let index = EXCEPTION_BUFFER.exception_stack[EXCEPTION_BUFFER.exception_count - 1];
|
||||||
let exception = &exception_info.exception.unwrap();
|
assert!(index != -1);
|
||||||
|
let exception = EXCEPTION_BUFFER.exceptions[index as usize].as_ref().unwrap();
|
||||||
|
let id = exception.id;
|
||||||
|
|
||||||
let name_ptr = exception.name.as_ptr();
|
let eh_action = match dwarf::find_eh_action(lsda, &eh_context, id) {
|
||||||
let len = exception.name.len();
|
|
||||||
let eh_action = match dwarf::find_eh_action(lsda, &eh_context, name_ptr, len) {
|
|
||||||
Ok(action) => action,
|
Ok(action) => action,
|
||||||
Err(_) => return uw::_URC_FATAL_PHASE1_ERROR,
|
Err(_) => return uw::_URC_FATAL_PHASE1_ERROR,
|
||||||
};
|
};
|
||||||
|
|
||||||
if actions as u32 & uw::_UA_SEARCH_PHASE as u32 != 0 {
|
|
||||||
match eh_action {
|
|
||||||
EHAction::None |
|
|
||||||
EHAction::Cleanup(_) => return uw::_URC_CONTINUE_UNWIND,
|
|
||||||
EHAction::Catch(_) => return uw::_URC_HANDLER_FOUND,
|
|
||||||
EHAction::Terminate => return uw::_URC_FATAL_PHASE1_ERROR,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
match eh_action {
|
match eh_action {
|
||||||
EHAction::None => return uw::_URC_CONTINUE_UNWIND,
|
EHAction::None => return uw::_URC_CONTINUE_UNWIND,
|
||||||
EHAction::Cleanup(lpad) |
|
EHAction::Cleanup(lpad) |
|
||||||
EHAction::Catch(lpad) => {
|
EHAction::Catch(lpad) => {
|
||||||
if actions as u32 & uw::_UA_HANDLER_FRAME as u32 != 0 {
|
|
||||||
exception_info.handled = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pass a pair of the unwinder exception and ARTIQ exception
|
// Pass a pair of the unwinder exception and ARTIQ exception
|
||||||
// (which immediately follows).
|
// (which immediately follows).
|
||||||
uw::_Unwind_SetGR(context, UNWIND_DATA_REG.0,
|
uw::_Unwind_SetGR(context, UNWIND_DATA_REG.0,
|
||||||
|
@ -123,95 +148,210 @@ pub extern fn personality(version: c_int,
|
||||||
EHAction::Terminate => return uw::_URC_FATAL_PHASE2_ERROR,
|
EHAction::Terminate => return uw::_URC_FATAL_PHASE2_ERROR,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[export_name="__artiq_raise"]
|
||||||
|
#[unwind(allowed)]
|
||||||
|
pub unsafe extern fn raise(exception: *const Exception) -> ! {
|
||||||
|
let count = EXCEPTION_BUFFER.exception_count;
|
||||||
|
let stack = &mut EXCEPTION_BUFFER.exception_stack;
|
||||||
|
let diff = exception as isize - EXCEPTION_BUFFER.exceptions.as_ptr() as isize;
|
||||||
|
if 0 <= diff && diff <= (mem::size_of::<Option<Exception>>() * MAX_INFLIGHT_EXCEPTIONS) as isize {
|
||||||
|
let index = diff / (mem::size_of::<Option<Exception>>() as isize);
|
||||||
|
let mut found = false;
|
||||||
|
for i in 0..=MAX_INFLIGHT_EXCEPTIONS + 1 {
|
||||||
|
if found {
|
||||||
|
if stack[i] == -1 {
|
||||||
|
stack[i - 1] = index;
|
||||||
|
assert!(i == count);
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
stack[i - 1] = stack[i];
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
if stack[i] == index {
|
||||||
|
found = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert!(found);
|
||||||
|
let _result = _Unwind_ForcedUnwind(&mut EXCEPTION_BUFFER.uw_exceptions[stack[count - 1] as usize],
|
||||||
|
stop_fn, core::ptr::null_mut());
|
||||||
|
} else {
|
||||||
|
if count < MAX_INFLIGHT_EXCEPTIONS {
|
||||||
|
let exception = &*exception;
|
||||||
|
for (i, slot) in EXCEPTION_BUFFER.exceptions.iter_mut().enumerate() {
|
||||||
|
// we should always be able to find a slot
|
||||||
|
if slot.is_none() {
|
||||||
|
*slot = Some(
|
||||||
|
*mem::transmute::<*const Exception, *const Exception<'static>>
|
||||||
|
(exception));
|
||||||
|
EXCEPTION_BUFFER.exception_stack[count] = i as isize;
|
||||||
|
EXCEPTION_BUFFER.uw_exceptions[i].private =
|
||||||
|
[0; uw::unwinder_private_data_size];
|
||||||
|
EXCEPTION_BUFFER.stack_pointers[i] = StackPointerBacktrace {
|
||||||
|
stack_pointer: 0,
|
||||||
|
initial_backtrace_size: EXCEPTION_BUFFER.backtrace_size,
|
||||||
|
current_backtrace_size: 0,
|
||||||
|
};
|
||||||
|
EXCEPTION_BUFFER.exception_count += 1;
|
||||||
|
let _result = _Unwind_ForcedUnwind(&mut EXCEPTION_BUFFER.uw_exceptions[i],
|
||||||
|
stop_fn, core::ptr::null_mut());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// TODO: better reporting?
|
||||||
|
let exception = Exception {
|
||||||
|
id: get_exception_id("RuntimeError"),
|
||||||
|
file: file!().as_c_slice(),
|
||||||
|
line: line!(),
|
||||||
|
column: column!(),
|
||||||
|
// https://github.com/rust-lang/rfcs/pull/1719
|
||||||
|
function: "__artiq_raise".as_c_slice(),
|
||||||
|
message: "too many nested exceptions".as_c_slice(),
|
||||||
|
param: [0, 0, 0]
|
||||||
|
};
|
||||||
|
EXCEPTION_BUFFER.exceptions[MAX_INFLIGHT_EXCEPTIONS] = Some(mem::transmute(exception));
|
||||||
|
EXCEPTION_BUFFER.stack_pointers[MAX_INFLIGHT_EXCEPTIONS] = Default::default();
|
||||||
|
EXCEPTION_BUFFER.exception_count += 1;
|
||||||
|
uncaught_exception()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
unreachable!();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[export_name="__artiq_resume"]
|
||||||
|
#[unwind(allowed)]
|
||||||
|
pub unsafe extern fn resume() -> ! {
|
||||||
|
assert!(EXCEPTION_BUFFER.exception_count != 0);
|
||||||
|
let i = EXCEPTION_BUFFER.exception_stack[EXCEPTION_BUFFER.exception_count - 1];
|
||||||
|
assert!(i != -1);
|
||||||
|
let _result = _Unwind_ForcedUnwind(&mut EXCEPTION_BUFFER.uw_exceptions[i as usize],
|
||||||
|
stop_fn, core::ptr::null_mut());
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[export_name="__artiq_end_catch"]
|
||||||
|
#[unwind(allowed)]
|
||||||
|
pub unsafe extern fn end_catch() {
|
||||||
|
let mut count = EXCEPTION_BUFFER.exception_count;
|
||||||
|
assert!(count != 0);
|
||||||
|
// we remove all exceptions with SP <= current exception SP
|
||||||
|
// i.e. the outer exception escapes the finally block
|
||||||
|
let index = EXCEPTION_BUFFER.exception_stack[count - 1] as usize;
|
||||||
|
EXCEPTION_BUFFER.exception_stack[count - 1] = -1;
|
||||||
|
EXCEPTION_BUFFER.exceptions[index] = None;
|
||||||
|
let outer_sp = EXCEPTION_BUFFER.stack_pointers
|
||||||
|
[index].stack_pointer;
|
||||||
|
count -= 1;
|
||||||
|
for i in (0..count).rev() {
|
||||||
|
let index = EXCEPTION_BUFFER.exception_stack[i];
|
||||||
|
assert!(index != -1);
|
||||||
|
let index = index as usize;
|
||||||
|
let sp = EXCEPTION_BUFFER.stack_pointers[index].stack_pointer;
|
||||||
|
if sp >= outer_sp {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
EXCEPTION_BUFFER.exceptions[index] = None;
|
||||||
|
EXCEPTION_BUFFER.exception_stack[i] = -1;
|
||||||
|
count -= 1;
|
||||||
|
}
|
||||||
|
EXCEPTION_BUFFER.exception_count = count;
|
||||||
|
EXCEPTION_BUFFER.backtrace_size = if count > 0 {
|
||||||
|
let index = EXCEPTION_BUFFER.exception_stack[count - 1];
|
||||||
|
assert!(index != -1);
|
||||||
|
EXCEPTION_BUFFER.stack_pointers[index as usize].current_backtrace_size
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
extern fn cleanup(_unwind_code: uw::_Unwind_Reason_Code,
|
extern fn cleanup(_unwind_code: uw::_Unwind_Reason_Code,
|
||||||
uw_exception: *mut uw::_Unwind_Exception) {
|
_uw_exception: *mut uw::_Unwind_Exception) {
|
||||||
unsafe {
|
unimplemented!()
|
||||||
let exception_info = &mut *(uw_exception as *mut ExceptionInfo);
|
}
|
||||||
|
|
||||||
exception_info.exception = None;
|
fn uncaught_exception() -> ! {
|
||||||
|
unsafe {
|
||||||
|
// dump way to reorder the stack
|
||||||
|
for i in 0..EXCEPTION_BUFFER.exception_count {
|
||||||
|
if EXCEPTION_BUFFER.exception_stack[i] != i as isize {
|
||||||
|
// find the correct index
|
||||||
|
let index = EXCEPTION_BUFFER.exception_stack
|
||||||
|
.iter()
|
||||||
|
.position(|v| *v == i as isize).unwrap();
|
||||||
|
let a = EXCEPTION_BUFFER.exception_stack[index];
|
||||||
|
let b = EXCEPTION_BUFFER.exception_stack[i];
|
||||||
|
assert!(a != -1 && b != -1);
|
||||||
|
core::mem::swap(&mut EXCEPTION_BUFFER.exception_stack[index],
|
||||||
|
&mut EXCEPTION_BUFFER.exception_stack[i]);
|
||||||
|
core::mem::swap(&mut EXCEPTION_BUFFER.exceptions[a as usize],
|
||||||
|
&mut EXCEPTION_BUFFER.exceptions[b as usize]);
|
||||||
|
core::mem::swap(&mut EXCEPTION_BUFFER.stack_pointers[a as usize],
|
||||||
|
&mut EXCEPTION_BUFFER.stack_pointers[b as usize]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
unsafe {
|
||||||
|
::terminate(
|
||||||
|
EXCEPTION_BUFFER.exceptions[..EXCEPTION_BUFFER.exception_count].as_ref(),
|
||||||
|
EXCEPTION_BUFFER.stack_pointers[..EXCEPTION_BUFFER.exception_count].as_ref(),
|
||||||
|
EXCEPTION_BUFFER.backtrace[..EXCEPTION_BUFFER.backtrace_size].as_mut())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extern fn uncaught_exception(_version: c_int,
|
|
||||||
|
// stop function which would be executed when we unwind each frame
|
||||||
|
extern fn stop_fn(_version: c_int,
|
||||||
actions: uw::_Unwind_Action,
|
actions: uw::_Unwind_Action,
|
||||||
_uw_exception_class: uw::_Unwind_Exception_Class,
|
_uw_exception_class: uw::_Unwind_Exception_Class,
|
||||||
uw_exception: *mut uw::_Unwind_Exception,
|
_uw_exception: *mut uw::_Unwind_Exception,
|
||||||
context: *mut uw::_Unwind_Context,
|
context: *mut uw::_Unwind_Context,
|
||||||
_stop_parameter: *mut c_void)
|
_stop_parameter: *mut c_void) -> uw::_Unwind_Reason_Code {
|
||||||
-> uw::_Unwind_Reason_Code {
|
|
||||||
unsafe {
|
unsafe {
|
||||||
let exception_info = &mut *(uw_exception as *mut ExceptionInfo);
|
let backtrace_size = EXCEPTION_BUFFER.backtrace_size;
|
||||||
|
if backtrace_size < MAX_BACKTRACE_SIZE {
|
||||||
if exception_info.backtrace_size < exception_info.backtrace.len() {
|
|
||||||
let ip = uw::_Unwind_GetIP(context);
|
let ip = uw::_Unwind_GetIP(context);
|
||||||
exception_info.backtrace[exception_info.backtrace_size] = ip;
|
let fp = uw::_Unwind_GetGR(context, UNW_FP_REG);
|
||||||
exception_info.backtrace_size += 1;
|
if PAYLOAD_ADDRESS == 0 || ip > PAYLOAD_ADDRESS {
|
||||||
|
let ip = ip - PAYLOAD_ADDRESS;
|
||||||
|
EXCEPTION_BUFFER.backtrace[backtrace_size] = (ip, fp);
|
||||||
|
EXCEPTION_BUFFER.backtrace_size += 1;
|
||||||
|
let last_index = EXCEPTION_BUFFER.exception_stack[EXCEPTION_BUFFER.exception_count - 1];
|
||||||
|
assert!(last_index != -1);
|
||||||
|
let sp_info = &mut EXCEPTION_BUFFER.stack_pointers[last_index as usize];
|
||||||
|
sp_info.stack_pointer = fp;
|
||||||
|
sp_info.current_backtrace_size = backtrace_size + 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if actions as u32 & uw::_UA_END_OF_STACK as u32 != 0 {
|
if actions as u32 & uw::_UA_END_OF_STACK as u32 != 0 {
|
||||||
::terminate(&exception_info.exception.unwrap(),
|
uncaught_exception()
|
||||||
exception_info.backtrace[..exception_info.backtrace_size].as_mut())
|
|
||||||
} else {
|
} else {
|
||||||
uw::_URC_NO_REASON
|
uw::_URC_NO_REASON
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// We can unfortunately not use mem::zeroed in a static, so Option<> is used as a workaround.
|
static EXCEPTION_ID_LOOKUP: [(&str, u32); 10] = [
|
||||||
// See https://github.com/rust-lang/rust/issues/39498.
|
("RuntimeError", 0),
|
||||||
static mut INFLIGHT: ExceptionInfo = ExceptionInfo {
|
("RTIOUnderflow", 1),
|
||||||
uw_exception: uw::_Unwind_Exception {
|
("RTIOOverflow", 2),
|
||||||
exception_class: EXCEPTION_CLASS,
|
("RTIODestinationUnreachable", 3),
|
||||||
exception_cleanup: cleanup,
|
("DMAError", 4),
|
||||||
private: [0; uw::unwinder_private_data_size],
|
("I2CError", 5),
|
||||||
},
|
("CacheError", 6),
|
||||||
exception: None,
|
("SPIError", 7),
|
||||||
handled: true,
|
("ZeroDivisionError", 8),
|
||||||
backtrace: [0; MAX_BACKTRACE_SIZE],
|
("IndexError", 9)
|
||||||
backtrace_size: 0
|
];
|
||||||
};
|
|
||||||
|
|
||||||
#[export_name="__artiq_raise"]
|
pub fn get_exception_id(name: &str) -> u32 {
|
||||||
#[unwind(allowed)]
|
for (n, id) in EXCEPTION_ID_LOOKUP.iter() {
|
||||||
pub unsafe extern fn raise(exception: *const Exception) -> ! {
|
if *n == name {
|
||||||
// Zing! The Exception<'a> to Exception<'static> transmute is not really sound in case
|
return *id
|
||||||
// the exception is ever captured. Fortunately, they currently aren't, and we save
|
}
|
||||||
// on the hassle of having to allocate exceptions somewhere except on stack.
|
}
|
||||||
INFLIGHT.exception = Some(mem::transmute::<Exception, Exception<'static>>(*exception));
|
unimplemented!("unallocated internal exception id")
|
||||||
INFLIGHT.handled = false;
|
|
||||||
|
|
||||||
let result = uw::_Unwind_RaiseException(&mut INFLIGHT.uw_exception);
|
|
||||||
assert!(result == uw::_URC_END_OF_STACK);
|
|
||||||
|
|
||||||
INFLIGHT.backtrace_size = 0;
|
|
||||||
let _result = _Unwind_ForcedUnwind(&mut INFLIGHT.uw_exception,
|
|
||||||
uncaught_exception, ptr::null_mut());
|
|
||||||
unreachable!()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[export_name="__artiq_reraise"]
|
|
||||||
#[unwind(allowed)]
|
|
||||||
pub unsafe extern fn reraise() -> ! {
|
|
||||||
use cslice::AsCSlice;
|
|
||||||
|
|
||||||
if INFLIGHT.handled {
|
|
||||||
match INFLIGHT.exception {
|
|
||||||
Some(ref exception) => raise(exception),
|
|
||||||
None => raise(&Exception {
|
|
||||||
name: "0:artiq.coredevice.exceptions.RuntimeError".as_c_slice(),
|
|
||||||
file: file!().as_c_slice(),
|
|
||||||
line: line!(),
|
|
||||||
column: column!(),
|
|
||||||
// https://github.com/rust-lang/rfcs/pull/1719
|
|
||||||
function: "__artiq_reraise".as_c_slice(),
|
|
||||||
message: "No active exception to reraise".as_c_slice(),
|
|
||||||
param: [0, 0, 0]
|
|
||||||
})
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
uw::_Unwind_Resume(&mut INFLIGHT.uw_exception)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
#![feature(lang_items, llvm_asm, panic_unwind, libc, unwind_attributes,
|
#![feature(lang_items, llvm_asm, panic_unwind, libc, unwind_attributes,
|
||||||
panic_info_message, nll)]
|
panic_info_message, nll, const_in_array_repeat_expressions)]
|
||||||
#![no_std]
|
#![no_std]
|
||||||
|
|
||||||
extern crate libc;
|
extern crate libc;
|
||||||
|
@ -80,8 +80,9 @@ macro_rules! println {
|
||||||
macro_rules! raise {
|
macro_rules! raise {
|
||||||
($name:expr, $message:expr, $param0:expr, $param1:expr, $param2:expr) => ({
|
($name:expr, $message:expr, $param0:expr, $param1:expr, $param2:expr) => ({
|
||||||
use cslice::AsCSlice;
|
use cslice::AsCSlice;
|
||||||
|
let name_id = $crate::eh_artiq::get_exception_id($name);
|
||||||
let exn = $crate::eh_artiq::Exception {
|
let exn = $crate::eh_artiq::Exception {
|
||||||
name: concat!("0:artiq.coredevice.exceptions.", $name).as_c_slice(),
|
id: name_id,
|
||||||
file: file!().as_c_slice(),
|
file: file!().as_c_slice(),
|
||||||
line: line!(),
|
line: line!(),
|
||||||
column: column!(),
|
column: column!(),
|
||||||
|
@ -164,12 +165,12 @@ extern fn rpc_recv(slot: *mut ()) -> usize {
|
||||||
&Err(ref exception) =>
|
&Err(ref exception) =>
|
||||||
unsafe {
|
unsafe {
|
||||||
eh_artiq::raise(&eh_artiq::Exception {
|
eh_artiq::raise(&eh_artiq::Exception {
|
||||||
name: exception.name.as_bytes().as_c_slice(),
|
id: exception.id,
|
||||||
file: exception.file.as_bytes().as_c_slice(),
|
file: exception.file,
|
||||||
line: exception.line,
|
line: exception.line,
|
||||||
column: exception.column,
|
column: exception.column,
|
||||||
function: exception.function.as_bytes().as_c_slice(),
|
function: exception.function,
|
||||||
message: exception.message.as_bytes().as_c_slice(),
|
message: exception.message,
|
||||||
param: exception.param
|
param: exception.param
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -177,27 +178,13 @@ extern fn rpc_recv(slot: *mut ()) -> usize {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn terminate(exception: &eh_artiq::Exception, backtrace: &mut [usize]) -> ! {
|
fn terminate(exceptions: &'static [Option<eh_artiq::Exception<'static>>],
|
||||||
let mut cursor = 0;
|
stack_pointers: &'static [eh_artiq::StackPointerBacktrace],
|
||||||
for index in 0..backtrace.len() {
|
backtrace: &mut [(usize, usize)]) -> ! {
|
||||||
if backtrace[index] > kernel_proto::KERNELCPU_PAYLOAD_ADDRESS {
|
|
||||||
backtrace[cursor] = backtrace[index] - kernel_proto::KERNELCPU_PAYLOAD_ADDRESS;
|
|
||||||
cursor += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let backtrace = &mut backtrace.as_mut()[0..cursor];
|
|
||||||
|
|
||||||
send(&RunException {
|
send(&RunException {
|
||||||
exception: kernel_proto::Exception {
|
exceptions,
|
||||||
name: str::from_utf8(exception.name.as_ref()).unwrap(),
|
stack_pointers,
|
||||||
file: str::from_utf8(exception.file.as_ref()).unwrap(),
|
backtrace
|
||||||
line: exception.line,
|
|
||||||
column: exception.column,
|
|
||||||
function: str::from_utf8(exception.function.as_ref()).unwrap(),
|
|
||||||
message: str::from_utf8(exception.message.as_ref()).unwrap(),
|
|
||||||
param: exception.param,
|
|
||||||
},
|
|
||||||
backtrace: backtrace
|
|
||||||
});
|
});
|
||||||
loop {}
|
loop {}
|
||||||
}
|
}
|
||||||
|
@ -472,6 +459,7 @@ unsafe fn attribute_writeback(typeinfo: *const ()) {
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub unsafe fn main() {
|
pub unsafe fn main() {
|
||||||
|
eh_artiq::reset_exception_buffer(KERNELCPU_PAYLOAD_ADDRESS);
|
||||||
let image = slice::from_raw_parts_mut(kernel_proto::KERNELCPU_PAYLOAD_ADDRESS as *mut u8,
|
let image = slice::from_raw_parts_mut(kernel_proto::KERNELCPU_PAYLOAD_ADDRESS as *mut u8,
|
||||||
kernel_proto::KERNELCPU_LAST_ADDRESS -
|
kernel_proto::KERNELCPU_LAST_ADDRESS -
|
||||||
kernel_proto::KERNELCPU_PAYLOAD_ADDRESS);
|
kernel_proto::KERNELCPU_PAYLOAD_ADDRESS);
|
||||||
|
|
|
@ -202,8 +202,7 @@ unsafe fn get_ttype_entry(
|
||||||
pub unsafe fn find_eh_action(
|
pub unsafe fn find_eh_action(
|
||||||
lsda: *const u8,
|
lsda: *const u8,
|
||||||
context: &EHContext<'_>,
|
context: &EHContext<'_>,
|
||||||
name: *const u8,
|
id: u32,
|
||||||
len: usize,
|
|
||||||
) -> Result<EHAction, ()> {
|
) -> Result<EHAction, ()> {
|
||||||
if lsda.is_null() {
|
if lsda.is_null() {
|
||||||
return Ok(EHAction::None);
|
return Ok(EHAction::None);
|
||||||
|
@ -275,20 +274,10 @@ pub unsafe fn find_eh_action(
|
||||||
return Ok(EHAction::Catch(lpad));
|
return Ok(EHAction::Catch(lpad));
|
||||||
}
|
}
|
||||||
// this seems to be target dependent
|
// this seems to be target dependent
|
||||||
let clause_ptr = *(catch_type as *const CSlice<u8>);
|
let clause_id = *(catch_type as *const u32);
|
||||||
let clause_name_ptr = (clause_ptr).as_ptr();
|
if clause_id == id {
|
||||||
let clause_name_len = (clause_ptr).len();
|
|
||||||
if clause_name_len == len {
|
|
||||||
if (clause_name_ptr == core::ptr::null() ||
|
|
||||||
clause_name_ptr == name ||
|
|
||||||
// somehow their name pointers might differ, but the content is the
|
|
||||||
// same
|
|
||||||
core::slice::from_raw_parts(clause_name_ptr, clause_name_len) ==
|
|
||||||
core::slice::from_raw_parts(name, len))
|
|
||||||
{
|
|
||||||
return Ok(EHAction::Catch(lpad));
|
return Ok(EHAction::Catch(lpad));
|
||||||
}
|
}
|
||||||
}
|
|
||||||
} else if ar_filter < 0 {
|
} else if ar_filter < 0 {
|
||||||
// FIXME: how to handle this?
|
// FIXME: how to handle this?
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -0,0 +1,47 @@
|
||||||
|
// ARTIQ Exception struct declaration
|
||||||
|
use cslice::CSlice;
|
||||||
|
|
||||||
|
// Note: CSlice within an exception may not be actual cslice, they may be strings that exist only
|
||||||
|
// in the host. If the length == usize:MAX, the pointer is actually a string key in the host.
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
pub struct Exception<'a> {
|
||||||
|
pub id: u32,
|
||||||
|
pub file: CSlice<'a, u8>,
|
||||||
|
pub line: u32,
|
||||||
|
pub column: u32,
|
||||||
|
pub function: CSlice<'a, u8>,
|
||||||
|
pub message: CSlice<'a, u8>,
|
||||||
|
pub param: [i64; 3]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn str_err(_: core::str::Utf8Error) -> core::fmt::Error {
|
||||||
|
core::fmt::Error
|
||||||
|
}
|
||||||
|
|
||||||
|
fn exception_str<'a>(s: &'a CSlice<'a, u8>) -> Result<&'a str, core::str::Utf8Error> {
|
||||||
|
if s.len() == usize::MAX {
|
||||||
|
Ok("<host string>")
|
||||||
|
} else {
|
||||||
|
core::str::from_utf8(s.as_ref())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> core::fmt::Debug for Exception<'a> {
|
||||||
|
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||||
|
write!(f, "Exception {} from {} in {}:{}:{}, message: {}",
|
||||||
|
self.id,
|
||||||
|
exception_str(&self.function).map_err(str_err)?,
|
||||||
|
exception_str(&self.file).map_err(str_err)?,
|
||||||
|
self.line, self.column,
|
||||||
|
exception_str(&self.message).map_err(str_err)?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, Default)]
|
||||||
|
pub struct StackPointerBacktrace {
|
||||||
|
pub stack_pointer: usize,
|
||||||
|
pub initial_backtrace_size: usize,
|
||||||
|
pub current_backtrace_size: usize,
|
||||||
|
}
|
||||||
|
|
|
@ -87,5 +87,5 @@ unsafe fn find_eh_action(context: *mut uw::_Unwind_Context) -> Result<EHAction,
|
||||||
get_text_start: &|| uw::_Unwind_GetTextRelBase(context),
|
get_text_start: &|| uw::_Unwind_GetTextRelBase(context),
|
||||||
get_data_start: &|| uw::_Unwind_GetDataRelBase(context),
|
get_data_start: &|| uw::_Unwind_GetDataRelBase(context),
|
||||||
};
|
};
|
||||||
dwarf::find_eh_action(lsda, &eh_context, core::ptr::null(), 0)
|
dwarf::find_eh_action(lsda, &eh_context, 0)
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,3 +7,4 @@ extern crate libc;
|
||||||
|
|
||||||
pub mod dwarf;
|
pub mod dwarf;
|
||||||
pub mod eh_rust;
|
pub mod eh_rust;
|
||||||
|
pub mod eh_artiq;
|
||||||
|
|
|
@ -15,6 +15,7 @@ cslice = { version = "0.3" }
|
||||||
log = { version = "0.4", default-features = false, optional = true }
|
log = { version = "0.4", default-features = false, optional = true }
|
||||||
io = { path = "../libio", features = ["byteorder"] }
|
io = { path = "../libio", features = ["byteorder"] }
|
||||||
dyld = { path = "../libdyld" }
|
dyld = { path = "../libdyld" }
|
||||||
|
eh = { path = "../libeh" }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
alloc = ["io/alloc"]
|
alloc = ["io/alloc"]
|
||||||
|
|
|
@ -6,17 +6,6 @@ pub const KERNELCPU_PAYLOAD_ADDRESS: usize = 0x45060000;
|
||||||
pub const KERNELCPU_LAST_ADDRESS: usize = 0x4fffffff;
|
pub const KERNELCPU_LAST_ADDRESS: usize = 0x4fffffff;
|
||||||
pub const KSUPPORT_HEADER_SIZE: usize = 0x80;
|
pub const KSUPPORT_HEADER_SIZE: usize = 0x80;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct Exception<'a> {
|
|
||||||
pub name: &'a str,
|
|
||||||
pub file: &'a str,
|
|
||||||
pub line: u32,
|
|
||||||
pub column: u32,
|
|
||||||
pub function: &'a str,
|
|
||||||
pub message: &'a str,
|
|
||||||
pub param: [i64; 3]
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Message<'a> {
|
pub enum Message<'a> {
|
||||||
LoadRequest(&'a [u8]),
|
LoadRequest(&'a [u8]),
|
||||||
|
@ -47,8 +36,9 @@ pub enum Message<'a> {
|
||||||
|
|
||||||
RunFinished,
|
RunFinished,
|
||||||
RunException {
|
RunException {
|
||||||
exception: Exception<'a>,
|
exceptions: &'a [Option<eh::eh_artiq::Exception<'a>>],
|
||||||
backtrace: &'a [usize]
|
stack_pointers: &'a [eh::eh_artiq::StackPointerBacktrace],
|
||||||
|
backtrace: &'a [(usize, usize)]
|
||||||
},
|
},
|
||||||
RunAborted,
|
RunAborted,
|
||||||
|
|
||||||
|
@ -59,7 +49,7 @@ pub enum Message<'a> {
|
||||||
data: *const *const ()
|
data: *const *const ()
|
||||||
},
|
},
|
||||||
RpcRecvRequest(*mut ()),
|
RpcRecvRequest(*mut ()),
|
||||||
RpcRecvReply(Result<usize, Exception<'a>>),
|
RpcRecvReply(Result<usize, eh::eh_artiq::Exception<'a>>),
|
||||||
RpcFlush,
|
RpcFlush,
|
||||||
|
|
||||||
CacheGetRequest { key: &'a str },
|
CacheGetRequest { key: &'a str },
|
||||||
|
|
|
@ -13,6 +13,7 @@ extern crate log;
|
||||||
extern crate byteorder;
|
extern crate byteorder;
|
||||||
extern crate io;
|
extern crate io;
|
||||||
extern crate dyld;
|
extern crate dyld;
|
||||||
|
extern crate eh;
|
||||||
|
|
||||||
// Internal protocols.
|
// Internal protocols.
|
||||||
pub mod kernel_proto;
|
pub mod kernel_proto;
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
use core::str::Utf8Error;
|
use core::str::Utf8Error;
|
||||||
use alloc::{vec::Vec, string::String};
|
use alloc::vec::Vec;
|
||||||
|
use eh::eh_artiq::{Exception, StackPointerBacktrace};
|
||||||
|
use cslice::CSlice;
|
||||||
|
|
||||||
use io::{Read, ProtoRead, Write, ProtoWrite, Error as IoError, ReadStringError};
|
use io::{Read, ProtoRead, Write, ProtoWrite, Error as IoError, ReadStringError};
|
||||||
|
|
||||||
|
@ -70,13 +72,13 @@ pub enum Request {
|
||||||
|
|
||||||
RpcReply { tag: Vec<u8> },
|
RpcReply { tag: Vec<u8> },
|
||||||
RpcException {
|
RpcException {
|
||||||
name: String,
|
id: u32,
|
||||||
message: String,
|
message: u32,
|
||||||
param: [i64; 3],
|
param: [i64; 3],
|
||||||
file: String,
|
file: u32,
|
||||||
line: u32,
|
line: u32,
|
||||||
column: u32,
|
column: u32,
|
||||||
function: String,
|
function: u32,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -95,14 +97,9 @@ pub enum Reply<'a> {
|
||||||
},
|
},
|
||||||
KernelStartupFailed,
|
KernelStartupFailed,
|
||||||
KernelException {
|
KernelException {
|
||||||
name: &'a str,
|
exceptions: &'a [Option<Exception<'a>>],
|
||||||
message: &'a str,
|
stack_pointers: &'a [StackPointerBacktrace],
|
||||||
param: [i64; 3],
|
backtrace: &'a [(usize, usize)],
|
||||||
file: &'a str,
|
|
||||||
line: u32,
|
|
||||||
column: u32,
|
|
||||||
function: &'a str,
|
|
||||||
backtrace: &'a [usize],
|
|
||||||
async_errors: u8
|
async_errors: u8
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -126,15 +123,15 @@ impl Request {
|
||||||
tag: reader.read_bytes()?
|
tag: reader.read_bytes()?
|
||||||
},
|
},
|
||||||
8 => Request::RpcException {
|
8 => Request::RpcException {
|
||||||
name: reader.read_string()?,
|
id: reader.read_u32()?,
|
||||||
message: reader.read_string()?,
|
message: reader.read_u32()?,
|
||||||
param: [reader.read_u64()? as i64,
|
param: [reader.read_u64()? as i64,
|
||||||
reader.read_u64()? as i64,
|
reader.read_u64()? as i64,
|
||||||
reader.read_u64()? as i64],
|
reader.read_u64()? as i64],
|
||||||
file: reader.read_string()?,
|
file: reader.read_u32()?,
|
||||||
line: reader.read_u32()?,
|
line: reader.read_u32()?,
|
||||||
column: reader.read_u32()?,
|
column: reader.read_u32()?,
|
||||||
function: reader.read_string()?
|
function: reader.read_u32()?
|
||||||
},
|
},
|
||||||
|
|
||||||
ty => return Err(Error::UnknownPacket(ty))
|
ty => return Err(Error::UnknownPacket(ty))
|
||||||
|
@ -142,6 +139,18 @@ impl Request {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn write_exception_string<'a, W>(writer: &mut W, s: &CSlice<'a, u8>) -> Result<(), IoError<W::WriteError>>
|
||||||
|
where W: Write + ?Sized
|
||||||
|
{
|
||||||
|
if s.len() == usize::MAX {
|
||||||
|
writer.write_u32(u32::MAX)?;
|
||||||
|
writer.write_u32(s.as_ptr() as u32)?;
|
||||||
|
} else {
|
||||||
|
writer.write_string(core::str::from_utf8(s.as_ref()).unwrap())?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
impl<'a> Reply<'a> {
|
impl<'a> Reply<'a> {
|
||||||
pub fn write_to<W>(&self, writer: &mut W) -> Result<(), IoError<W::WriteError>>
|
pub fn write_to<W>(&self, writer: &mut W) -> Result<(), IoError<W::WriteError>>
|
||||||
where W: Write + ?Sized
|
where W: Write + ?Sized
|
||||||
|
@ -171,22 +180,36 @@ impl<'a> Reply<'a> {
|
||||||
writer.write_u8(8)?;
|
writer.write_u8(8)?;
|
||||||
},
|
},
|
||||||
Reply::KernelException {
|
Reply::KernelException {
|
||||||
name, message, param, file, line, column, function, backtrace,
|
exceptions,
|
||||||
|
stack_pointers,
|
||||||
|
backtrace,
|
||||||
async_errors
|
async_errors
|
||||||
} => {
|
} => {
|
||||||
writer.write_u8(9)?;
|
writer.write_u8(9)?;
|
||||||
writer.write_string(name)?;
|
writer.write_u32(exceptions.len() as u32)?;
|
||||||
writer.write_string(message)?;
|
for exception in exceptions.iter() {
|
||||||
writer.write_u64(param[0] as u64)?;
|
let exception = exception.as_ref().unwrap();
|
||||||
writer.write_u64(param[1] as u64)?;
|
writer.write_u32(exception.id as u32)?;
|
||||||
writer.write_u64(param[2] as u64)?;
|
write_exception_string(writer, &exception.message)?;
|
||||||
writer.write_string(file)?;
|
writer.write_u64(exception.param[0] as u64)?;
|
||||||
writer.write_u32(line)?;
|
writer.write_u64(exception.param[1] as u64)?;
|
||||||
writer.write_u32(column)?;
|
writer.write_u64(exception.param[2] as u64)?;
|
||||||
writer.write_string(function)?;
|
write_exception_string(writer, &exception.file)?;
|
||||||
|
writer.write_u32(exception.line)?;
|
||||||
|
writer.write_u32(exception.column)?;
|
||||||
|
write_exception_string(writer, &exception.function)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
for sp in stack_pointers.iter() {
|
||||||
|
writer.write_u32(sp.stack_pointer as u32)?;
|
||||||
|
writer.write_u32(sp.initial_backtrace_size as u32)?;
|
||||||
|
writer.write_u32(sp.current_backtrace_size as u32)?;
|
||||||
|
}
|
||||||
|
|
||||||
writer.write_u32(backtrace.len() as u32)?;
|
writer.write_u32(backtrace.len() as u32)?;
|
||||||
for &addr in backtrace {
|
for &(addr, sp) in backtrace {
|
||||||
writer.write_u32(addr as u32)?
|
writer.write_u32(addr as u32)?;
|
||||||
|
writer.write_u32(sp as u32)?;
|
||||||
}
|
}
|
||||||
writer.write_u8(async_errors)?;
|
writer.write_u8(async_errors)?;
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use core::{mem, str, cell::{Cell, RefCell}, fmt::Write as FmtWrite};
|
use core::{mem, str, cell::{Cell, RefCell}, fmt::Write as FmtWrite};
|
||||||
use alloc::{vec::Vec, string::String};
|
use alloc::{vec::Vec, string::String};
|
||||||
use byteorder::{ByteOrder, NativeEndian};
|
use byteorder::{ByteOrder, NativeEndian};
|
||||||
|
use cslice::CSlice;
|
||||||
|
|
||||||
use io::{Read, Write, Error as IoError};
|
use io::{Read, Write, Error as IoError};
|
||||||
use board_misoc::{ident, cache, config};
|
use board_misoc::{ident, cache, config};
|
||||||
|
@ -291,7 +292,7 @@ fn process_host_message(io: &Io,
|
||||||
}
|
}
|
||||||
|
|
||||||
host::Request::RpcException {
|
host::Request::RpcException {
|
||||||
name, message, param, file, line, column, function
|
id, message, param, file, line, column, function
|
||||||
} => {
|
} => {
|
||||||
if session.kernel_state != KernelState::RpcWait {
|
if session.kernel_state != KernelState::RpcWait {
|
||||||
unexpected!("unsolicited RPC reply")
|
unexpected!("unsolicited RPC reply")
|
||||||
|
@ -305,16 +306,18 @@ fn process_host_message(io: &Io,
|
||||||
}
|
}
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let exn = kern::Exception {
|
unsafe {
|
||||||
name: name.as_ref(),
|
let exn = eh::eh_artiq::Exception {
|
||||||
message: message.as_ref(),
|
id: id,
|
||||||
|
message: CSlice::new(message as *const u8, usize::MAX),
|
||||||
param: param,
|
param: param,
|
||||||
file: file.as_ref(),
|
file: CSlice::new(file as *const u8, usize::MAX),
|
||||||
line: line,
|
line: line,
|
||||||
column: column,
|
column: column,
|
||||||
function: function.as_ref()
|
function: CSlice::new(function as *const u8, usize::MAX),
|
||||||
};
|
};
|
||||||
kern_send(io, &kern::RpcRecvReply(Err(exn)))?;
|
kern_send(io, &kern::RpcRecvReply(Err(exn)))?;
|
||||||
|
}
|
||||||
|
|
||||||
session.kernel_state = KernelState::Running
|
session.kernel_state = KernelState::Running
|
||||||
}
|
}
|
||||||
|
@ -438,7 +441,8 @@ fn process_kern_message(io: &Io, aux_mutex: &Mutex,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
&kern::RunException {
|
&kern::RunException {
|
||||||
exception: kern::Exception { name, message, param, file, line, column, function },
|
exceptions,
|
||||||
|
stack_pointers,
|
||||||
backtrace
|
backtrace
|
||||||
} => {
|
} => {
|
||||||
unsafe { kernel::stop() }
|
unsafe { kernel::stop() }
|
||||||
|
@ -448,19 +452,15 @@ fn process_kern_message(io: &Io, aux_mutex: &Mutex,
|
||||||
match stream {
|
match stream {
|
||||||
None => {
|
None => {
|
||||||
error!("exception in flash kernel");
|
error!("exception in flash kernel");
|
||||||
error!("{}: {} {:?}", name, message, param);
|
for exception in exceptions {
|
||||||
error!("at {}:{}:{} in {}", file, line, column, function);
|
error!("{:?}", exception.unwrap());
|
||||||
|
}
|
||||||
return Ok(true)
|
return Ok(true)
|
||||||
},
|
},
|
||||||
Some(ref mut stream) => {
|
Some(ref mut stream) => {
|
||||||
host_write(stream, host::Reply::KernelException {
|
host_write(stream, host::Reply::KernelException {
|
||||||
name: name,
|
exceptions: exceptions,
|
||||||
message: message,
|
stack_pointers: stack_pointers,
|
||||||
param: param,
|
|
||||||
file: file,
|
|
||||||
line: line,
|
|
||||||
column: column,
|
|
||||||
function: function,
|
|
||||||
backtrace: backtrace,
|
backtrace: backtrace,
|
||||||
async_errors: unsafe { get_async_errors() }
|
async_errors: unsafe { get_async_errors() }
|
||||||
}).map_err(|e| e.into())
|
}).map_err(|e| e.into())
|
||||||
|
|
|
@ -330,8 +330,7 @@ class HasEnvironment:
|
||||||
|
|
||||||
@rpc(flags={"async"})
|
@rpc(flags={"async"})
|
||||||
def set_dataset(self, key, value,
|
def set_dataset(self, key, value,
|
||||||
broadcast=False, persist=False, archive=True,
|
broadcast=False, persist=False, archive=True):
|
||||||
hdf5_options=None):
|
|
||||||
"""Sets the contents and handling modes of a dataset.
|
"""Sets the contents and handling modes of a dataset.
|
||||||
|
|
||||||
Datasets must be scalars (``bool``, ``int``, ``float`` or NumPy scalar)
|
Datasets must be scalars (``bool``, ``int``, ``float`` or NumPy scalar)
|
||||||
|
@ -343,16 +342,8 @@ class HasEnvironment:
|
||||||
broadcast.
|
broadcast.
|
||||||
:param archive: the data is saved into the local storage of the current
|
:param archive: the data is saved into the local storage of the current
|
||||||
run (archived as a HDF5 file).
|
run (archived as a HDF5 file).
|
||||||
:param hdf5_options: dict of keyword arguments to pass to
|
|
||||||
:meth:`h5py.Group.create_dataset`. For example, pass ``{"compression": "gzip"}``
|
|
||||||
to enable transparent zlib compression of this dataset in the HDF5 archive.
|
|
||||||
See the `h5py documentation <https://docs.h5py.org/en/stable/high/group.html#h5py.Group.create_dataset>`_
|
|
||||||
for a list of valid options.
|
|
||||||
"""
|
"""
|
||||||
|
self.__dataset_mgr.set(key, value, broadcast, persist, archive)
|
||||||
self.__dataset_mgr.set(
|
|
||||||
key, value, broadcast, persist, archive, hdf5_options
|
|
||||||
)
|
|
||||||
|
|
||||||
@rpc(flags={"async"})
|
@rpc(flags={"async"})
|
||||||
def mutate_dataset(self, key, index, value):
|
def mutate_dataset(self, key, index, value):
|
||||||
|
|
|
@ -35,15 +35,6 @@ class DeviceDB:
|
||||||
return desc
|
return desc
|
||||||
|
|
||||||
|
|
||||||
def make_dataset(*, persist=False, value=None, hdf5_options=None):
|
|
||||||
"PYON-serializable representation of a dataset in the DatasetDB"
|
|
||||||
return {
|
|
||||||
"persist": persist,
|
|
||||||
"value": value,
|
|
||||||
"hdf5_options": hdf5_options or {},
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class DatasetDB(TaskObject):
|
class DatasetDB(TaskObject):
|
||||||
def __init__(self, persist_file, autosave_period=30):
|
def __init__(self, persist_file, autosave_period=30):
|
||||||
self.persist_file = persist_file
|
self.persist_file = persist_file
|
||||||
|
@ -53,23 +44,10 @@ class DatasetDB(TaskObject):
|
||||||
file_data = pyon.load_file(self.persist_file)
|
file_data = pyon.load_file(self.persist_file)
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
file_data = dict()
|
file_data = dict()
|
||||||
self.data = Notifier(
|
self.data = Notifier({k: (True, v) for k, v in file_data.items()})
|
||||||
{
|
|
||||||
k: make_dataset(
|
|
||||||
persist=True,
|
|
||||||
value=v["value"],
|
|
||||||
hdf5_options=v["hdf5_options"]
|
|
||||||
)
|
|
||||||
for k, v in file_data.items()
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
def save(self):
|
def save(self):
|
||||||
data = {
|
data = {k: v[1] for k, v in self.data.raw_view.items() if v[0]}
|
||||||
k: d
|
|
||||||
for k, d in self.data.raw_view.items()
|
|
||||||
if d["persist"]
|
|
||||||
}
|
|
||||||
pyon.store_file(self.persist_file, data)
|
pyon.store_file(self.persist_file, data)
|
||||||
|
|
||||||
async def _do(self):
|
async def _do(self):
|
||||||
|
@ -81,23 +59,20 @@ class DatasetDB(TaskObject):
|
||||||
self.save()
|
self.save()
|
||||||
|
|
||||||
def get(self, key):
|
def get(self, key):
|
||||||
return self.data.raw_view[key]
|
return self.data.raw_view[key][1]
|
||||||
|
|
||||||
def update(self, mod):
|
def update(self, mod):
|
||||||
process_mod(self.data, mod)
|
process_mod(self.data, mod)
|
||||||
|
|
||||||
# convenience functions (update() can be used instead)
|
# convenience functions (update() can be used instead)
|
||||||
def set(self, key, value, persist=None, hdf5_options=None):
|
def set(self, key, value, persist=None):
|
||||||
if persist is None:
|
if persist is None:
|
||||||
if key in self.data.raw_view:
|
if key in self.data.raw_view:
|
||||||
persist = self.data.raw_view[key]["persist"]
|
persist = self.data.raw_view[key][0]
|
||||||
else:
|
else:
|
||||||
persist = False
|
persist = False
|
||||||
self.data[key] = make_dataset(
|
self.data[key] = (persist, value)
|
||||||
persist=persist,
|
|
||||||
value=value,
|
|
||||||
hdf5_options=hdf5_options,
|
|
||||||
)
|
|
||||||
|
|
||||||
def delete(self, key):
|
def delete(self, key):
|
||||||
del self.data[key]
|
del self.data[key]
|
||||||
|
#
|
||||||
|
|
|
@ -8,12 +8,9 @@ from operator import setitem
|
||||||
import importlib
|
import importlib
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
import numpy as np
|
|
||||||
|
|
||||||
from sipyco.sync_struct import Notifier
|
from sipyco.sync_struct import Notifier
|
||||||
from sipyco.pc_rpc import AutoTarget, Client, BestEffortClient
|
from sipyco.pc_rpc import AutoTarget, Client, BestEffortClient
|
||||||
|
|
||||||
from artiq.master.databases import make_dataset
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -118,26 +115,17 @@ class DatasetManager:
|
||||||
self.ddb = ddb
|
self.ddb = ddb
|
||||||
self._broadcaster.publish = ddb.update
|
self._broadcaster.publish = ddb.update
|
||||||
|
|
||||||
def set(self, key, value, broadcast=False, persist=False, archive=True,
|
def set(self, key, value, broadcast=False, persist=False, archive=True):
|
||||||
hdf5_options=None):
|
|
||||||
if persist:
|
if persist:
|
||||||
broadcast = True
|
broadcast = True
|
||||||
|
|
||||||
if broadcast:
|
if broadcast:
|
||||||
self._broadcaster[key] = make_dataset(
|
self._broadcaster[key] = persist, value
|
||||||
persist=persist,
|
|
||||||
value=value,
|
|
||||||
hdf5_options=hdf5_options,
|
|
||||||
)
|
|
||||||
elif key in self._broadcaster.raw_view:
|
elif key in self._broadcaster.raw_view:
|
||||||
del self._broadcaster[key]
|
del self._broadcaster[key]
|
||||||
|
|
||||||
if archive:
|
if archive:
|
||||||
self.local[key] = make_dataset(
|
self.local[key] = value
|
||||||
persist=persist,
|
|
||||||
value=value,
|
|
||||||
hdf5_options=hdf5_options,
|
|
||||||
)
|
|
||||||
elif key in self.local:
|
elif key in self.local:
|
||||||
del self.local[key]
|
del self.local[key]
|
||||||
|
|
||||||
|
@ -145,11 +133,11 @@ class DatasetManager:
|
||||||
target = self.local.get(key, None)
|
target = self.local.get(key, None)
|
||||||
if key in self._broadcaster.raw_view:
|
if key in self._broadcaster.raw_view:
|
||||||
if target is not None:
|
if target is not None:
|
||||||
assert target["value"] is self._broadcaster.raw_view[key]["value"]
|
assert target is self._broadcaster.raw_view[key][1]
|
||||||
return self._broadcaster[key]["value"]
|
return self._broadcaster[key][1]
|
||||||
if target is None:
|
if target is None:
|
||||||
raise KeyError("Cannot mutate nonexistent dataset '{}'".format(key))
|
raise KeyError("Cannot mutate nonexistent dataset '{}'".format(key))
|
||||||
return target["value"]
|
return target
|
||||||
|
|
||||||
def mutate(self, key, index, value):
|
def mutate(self, key, index, value):
|
||||||
target = self._get_mutation_target(key)
|
target = self._get_mutation_target(key)
|
||||||
|
@ -165,15 +153,15 @@ class DatasetManager:
|
||||||
|
|
||||||
def get(self, key, archive=False):
|
def get(self, key, archive=False):
|
||||||
if key in self.local:
|
if key in self.local:
|
||||||
return self.local[key]["value"]
|
return self.local[key]
|
||||||
|
|
||||||
dataset = self.ddb.get(key)
|
data = self.ddb.get(key)
|
||||||
if archive:
|
if archive:
|
||||||
if key in self.archive:
|
if key in self.archive:
|
||||||
logger.warning("Dataset '%s' is already in archive, "
|
logger.warning("Dataset '%s' is already in archive, "
|
||||||
"overwriting", key, stack_info=True)
|
"overwriting", key, stack_info=True)
|
||||||
self.archive[key] = dataset
|
self.archive[key] = data
|
||||||
return dataset["value"]
|
return data
|
||||||
|
|
||||||
def write_hdf5(self, f):
|
def write_hdf5(self, f):
|
||||||
datasets_group = f.create_group("datasets")
|
datasets_group = f.create_group("datasets")
|
||||||
|
@ -189,7 +177,7 @@ def _write(group, k, v):
|
||||||
# Add context to exception message when the user writes a dataset that is
|
# Add context to exception message when the user writes a dataset that is
|
||||||
# not representable in HDF5.
|
# not representable in HDF5.
|
||||||
try:
|
try:
|
||||||
group.create_dataset(k, data=v["value"], **v["hdf5_options"])
|
group[k] = v
|
||||||
except TypeError as e:
|
except TypeError as e:
|
||||||
raise TypeError("Error writing dataset '{}' of type '{}': {}".format(
|
raise TypeError("Error writing dataset '{}' of type '{}': {}".format(
|
||||||
k, type(v["value"]), e))
|
k, type(v), e))
|
||||||
|
|
|
@ -125,6 +125,57 @@ class _Pulses(EnvExperiment):
|
||||||
class _MyException(Exception):
|
class _MyException(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
class _NestedFinally(EnvExperiment):
|
||||||
|
def build(self, trace):
|
||||||
|
self.setattr_device("core")
|
||||||
|
self.trace = trace
|
||||||
|
|
||||||
|
def _trace(self, i):
|
||||||
|
self.trace.append(i)
|
||||||
|
|
||||||
|
@kernel
|
||||||
|
def run(self):
|
||||||
|
try:
|
||||||
|
try:
|
||||||
|
raise ValueError
|
||||||
|
finally:
|
||||||
|
try:
|
||||||
|
raise IndexError()
|
||||||
|
except ValueError:
|
||||||
|
self._trace(0)
|
||||||
|
except:
|
||||||
|
self._trace(1)
|
||||||
|
finally:
|
||||||
|
self._trace(2)
|
||||||
|
|
||||||
|
class _NestedExceptions(EnvExperiment):
|
||||||
|
def build(self, trace):
|
||||||
|
self.setattr_device("core")
|
||||||
|
self.trace = trace
|
||||||
|
|
||||||
|
def _trace(self, i):
|
||||||
|
self.trace.append(i)
|
||||||
|
|
||||||
|
@kernel
|
||||||
|
def run(self):
|
||||||
|
try:
|
||||||
|
try:
|
||||||
|
raise ValueError
|
||||||
|
except _MyException:
|
||||||
|
self._trace(0)
|
||||||
|
raise
|
||||||
|
finally:
|
||||||
|
try:
|
||||||
|
raise IndexError()
|
||||||
|
except ValueError:
|
||||||
|
self._trace(1)
|
||||||
|
raise
|
||||||
|
except IndexError:
|
||||||
|
self._trace(2)
|
||||||
|
except:
|
||||||
|
self._trace(3)
|
||||||
|
finally:
|
||||||
|
self._trace(4)
|
||||||
|
|
||||||
class _Exceptions(EnvExperiment):
|
class _Exceptions(EnvExperiment):
|
||||||
def build(self, trace):
|
def build(self, trace):
|
||||||
|
@ -253,6 +304,18 @@ class HostVsDeviceCase(ExperimentCase):
|
||||||
_run_on_host(_Exceptions, trace=t_host)
|
_run_on_host(_Exceptions, trace=t_host)
|
||||||
self.assertEqual(t_device, t_host)
|
self.assertEqual(t_device, t_host)
|
||||||
|
|
||||||
|
def test_nested_finally(self):
|
||||||
|
t_device, t_host = [], []
|
||||||
|
self.execute(_NestedFinally, trace=t_device)
|
||||||
|
_run_on_host(_NestedFinally, trace=t_host)
|
||||||
|
self.assertEqual(t_device, t_host)
|
||||||
|
|
||||||
|
def test_nested_exceptions(self):
|
||||||
|
t_device, t_host = [], []
|
||||||
|
self.execute(_NestedExceptions, trace=t_device)
|
||||||
|
_run_on_host(_NestedExceptions, trace=t_host)
|
||||||
|
self.assertEqual(t_device, t_host)
|
||||||
|
|
||||||
def test_rpc_exceptions(self):
|
def test_rpc_exceptions(self):
|
||||||
for f in self.execute, _run_on_host:
|
for f in self.execute, _run_on_host:
|
||||||
with self.assertRaises(_MyException):
|
with self.assertRaises(_MyException):
|
||||||
|
|
|
@ -3,9 +3,6 @@
|
||||||
import copy
|
import copy
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
import h5py
|
|
||||||
import numpy as np
|
|
||||||
|
|
||||||
from sipyco.sync_struct import process_mod
|
from sipyco.sync_struct import process_mod
|
||||||
|
|
||||||
from artiq.experiment import EnvExperiment
|
from artiq.experiment import EnvExperiment
|
||||||
|
@ -17,7 +14,7 @@ class MockDatasetDB:
|
||||||
self.data = dict()
|
self.data = dict()
|
||||||
|
|
||||||
def get(self, key):
|
def get(self, key):
|
||||||
return self.data[key]["value"]
|
return self.data[key][1]
|
||||||
|
|
||||||
def update(self, mod):
|
def update(self, mod):
|
||||||
# Copy mod before applying to avoid sharing references to objects
|
# Copy mod before applying to avoid sharing references to objects
|
||||||
|
@ -85,9 +82,9 @@ class ExperimentDatasetCase(unittest.TestCase):
|
||||||
def test_append_broadcast(self):
|
def test_append_broadcast(self):
|
||||||
self.exp.set(KEY, [], broadcast=True)
|
self.exp.set(KEY, [], broadcast=True)
|
||||||
self.exp.append(KEY, 0)
|
self.exp.append(KEY, 0)
|
||||||
self.assertEqual(self.dataset_db.data[KEY]["value"], [0])
|
self.assertEqual(self.dataset_db.data[KEY][1], [0])
|
||||||
self.exp.append(KEY, 1)
|
self.exp.append(KEY, 1)
|
||||||
self.assertEqual(self.dataset_db.data[KEY]["value"], [0, 1])
|
self.assertEqual(self.dataset_db.data[KEY][1], [0, 1])
|
||||||
|
|
||||||
def test_append_array(self):
|
def test_append_array(self):
|
||||||
for broadcast in (True, False):
|
for broadcast in (True, False):
|
||||||
|
@ -106,44 +103,3 @@ class ExperimentDatasetCase(unittest.TestCase):
|
||||||
with self.assertRaises(KeyError):
|
with self.assertRaises(KeyError):
|
||||||
self.exp.append(KEY, 0)
|
self.exp.append(KEY, 0)
|
||||||
|
|
||||||
def test_write_hdf5_options(self):
|
|
||||||
data = np.random.randint(0, 1024, 1024)
|
|
||||||
self.exp.set(
|
|
||||||
KEY,
|
|
||||||
data,
|
|
||||||
hdf5_options=dict(
|
|
||||||
compression="gzip",
|
|
||||||
compression_opts=6,
|
|
||||||
shuffle=True,
|
|
||||||
fletcher32=True
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
with h5py.File("test.h5", "a", "core", backing_store=False) as f:
|
|
||||||
self.dataset_mgr.write_hdf5(f)
|
|
||||||
|
|
||||||
self.assertTrue(np.array_equal(f["datasets"][KEY][()], data))
|
|
||||||
self.assertEqual(f["datasets"][KEY].compression, "gzip")
|
|
||||||
self.assertEqual(f["datasets"][KEY].compression_opts, 6)
|
|
||||||
self.assertTrue(f["datasets"][KEY].shuffle)
|
|
||||||
self.assertTrue(f["datasets"][KEY].fletcher32)
|
|
||||||
|
|
||||||
def test_write_hdf5_no_options(self):
|
|
||||||
data = np.random.randint(0, 1024, 1024)
|
|
||||||
self.exp.set(KEY, data)
|
|
||||||
|
|
||||||
with h5py.File("test.h5", "a", "core", backing_store=False) as f:
|
|
||||||
self.dataset_mgr.write_hdf5(f)
|
|
||||||
self.assertTrue(np.array_equal(f["datasets"][KEY][()], data))
|
|
||||||
self.assertIsNone(f["datasets"][KEY].compression)
|
|
||||||
|
|
||||||
def test_write_hdf5_invalid_type(self):
|
|
||||||
class CustomType:
|
|
||||||
def __init__(self, x):
|
|
||||||
self.x = x
|
|
||||||
|
|
||||||
self.exp.set(KEY, CustomType(42))
|
|
||||||
|
|
||||||
with h5py.File("test.h5", "w", "core", backing_store=False) as f:
|
|
||||||
with self.assertRaisesRegex(TypeError, "CustomType"):
|
|
||||||
self.dataset_mgr.write_hdf5(f)
|
|
||||||
|
|
|
@ -6,7 +6,6 @@ from time import time, sleep
|
||||||
|
|
||||||
from artiq.experiment import *
|
from artiq.experiment import *
|
||||||
from artiq.master.scheduler import Scheduler
|
from artiq.master.scheduler import Scheduler
|
||||||
from artiq.master.databases import make_dataset
|
|
||||||
|
|
||||||
|
|
||||||
class EmptyExperiment(EnvExperiment):
|
class EmptyExperiment(EnvExperiment):
|
||||||
|
@ -288,13 +287,8 @@ class SchedulerCase(unittest.TestCase):
|
||||||
nonlocal termination_ok
|
nonlocal termination_ok
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
mod,
|
mod,
|
||||||
{
|
{"action": "setitem", "key": "termination_ok",
|
||||||
"action": "setitem",
|
"value": (False, True), "path": []})
|
||||||
"key": "termination_ok",
|
|
||||||
"value": make_dataset(value=True),
|
|
||||||
"path": []
|
|
||||||
}
|
|
||||||
)
|
|
||||||
termination_ok = True
|
termination_ok = True
|
||||||
handlers = {
|
handlers = {
|
||||||
"update_dataset": check_termination
|
"update_dataset": check_termination
|
||||||
|
|
|
@ -189,7 +189,7 @@
|
||||||
cargoDeps = rustPlatform.fetchCargoTarball {
|
cargoDeps = rustPlatform.fetchCargoTarball {
|
||||||
name = "artiq-firmware-cargo-deps";
|
name = "artiq-firmware-cargo-deps";
|
||||||
src = "${self}/artiq/firmware";
|
src = "${self}/artiq/firmware";
|
||||||
sha256 = "sha256-Lf6M4M/jdRiO5MsWSoqtOSNfRIhbze+qvg4kaiiBWW4=";
|
sha256 = "sha256-YyycMsDzR+JRcMZJd6A/CRi2J9nKmaWY/KXUnAQaZ00=";
|
||||||
};
|
};
|
||||||
nativeBuildInputs = [
|
nativeBuildInputs = [
|
||||||
(pkgs.python3.withPackages(ps: [ migen misoc artiq ]))
|
(pkgs.python3.withPackages(ps: [ migen misoc artiq ]))
|
||||||
|
|
Loading…
Reference in New Issue