diff --git a/artiq/compiler/transforms/iodelay_estimator.py b/artiq/compiler/transforms/iodelay_estimator.py index 163093bb9..5ba867cc8 100644 --- a/artiq/compiler/transforms/iodelay_estimator.py +++ b/artiq/compiler/transforms/iodelay_estimator.py @@ -24,11 +24,11 @@ class IODelayEstimator(algorithm.Visitor): self.current_goto = None self.current_return = None - def evaluate(self, node, abort): + def evaluate(self, node, abort, context): if isinstance(node, asttyped.NumT): return iodelay.Const(node.n) elif isinstance(node, asttyped.CoerceT): - return self.evaluate(node.value, abort) + return self.evaluate(node.value, abort, context) elif isinstance(node, asttyped.NameT): if self.current_args is None: note = diagnostic.Diagnostic("note", @@ -48,8 +48,8 @@ class IODelayEstimator(algorithm.Visitor): ] abort(notes) elif isinstance(node, asttyped.BinOpT): - lhs = self.evaluate(node.left, abort) - rhs = self.evaluate(node.right, abort) + lhs = self.evaluate(node.left, abort, context) + rhs = self.evaluate(node.right, abort, context) if isinstance(node.op, ast.Add): return lhs + rhs elif isinstance(node.op, ast.Sub): @@ -62,12 +62,14 @@ class IODelayEstimator(algorithm.Visitor): return lhs // rhs else: note = diagnostic.Diagnostic("note", - "this operator is not supported", {}, + "this operator is not supported {context}", + {"context": context}, node.op.loc) abort([note]) else: note = diagnostic.Diagnostic("note", - "this expression is not supported", {}, + "this expression is not supported {context}", + {"context": context}, node.loc) abort([note]) @@ -143,14 +145,14 @@ class IODelayEstimator(algorithm.Visitor): def visit_LambdaT(self, node): self.visit_function(node.args, node.body, node.type.find(), node.loc) - def get_iterable_length(self, node): + def get_iterable_length(self, node, context): def abort(notes): self.abort("for statement cannot be interleaved because " - "trip count is indeterminate", + "iteration count is indeterminate", node.loc, notes) def evaluate(node): - return self.evaluate(node, abort) + return self.evaluate(node, abort, context) if isinstance(node, asttyped.CallT) and types.is_builtin(node.func.type, "range"): range_min, range_max, range_step = iodelay.Const(0), None, iodelay.Const(1) @@ -177,10 +179,11 @@ class IODelayEstimator(algorithm.Visitor): self.current_delay = old_delay else: if self.current_goto is not None: - self.abort("loop trip count is indeterminate because of control flow", + self.abort("loop iteration count is indeterminate because of control flow", self.current_goto.loc) - node.trip_count = self.get_iterable_length(node.iter).fold() + context = "in an iterable used in a for loop that is being interleaved" + node.trip_count = self.get_iterable_length(node.iter, context).fold() node.trip_interval = self.current_delay.fold() self.current_delay = old_delay + node.trip_interval * node.trip_count self.current_goto = old_goto @@ -231,6 +234,10 @@ class IODelayEstimator(algorithm.Visitor): # Interleave failures inside `with` statements are hard failures, # since there's no chance that the code will never actually execute # inside a `with` statement after all. + note = diagnostic.Diagnostic("note", + "while interleaving this 'with parallel:' statement", {}, + node.loc) + error.cause.notes += [note] self.engine.process(error.cause) flow_stmt = None @@ -258,15 +265,17 @@ class IODelayEstimator(algorithm.Visitor): def visit_CallT(self, node): typ = node.func.type.find() def abort(notes): - self.abort("this call cannot be interleaved because " + self.abort("call cannot be interleaved because " "an argument cannot be statically evaluated", node.loc, notes) if types.is_builtin(typ, "delay"): - value = self.evaluate(node.args[0], abort=abort) + value = self.evaluate(node.args[0], abort=abort, + context="as an argument for delay()") call_delay = iodelay.SToMU(value, ref_period=self.ref_period) elif types.is_builtin(typ, "delay_mu"): - value = self.evaluate(node.args[0], abort=abort) + value = self.evaluate(node.args[0], abort=abort, + context="as an argument for delay_mu()") call_delay = value elif not types.is_builtin(typ): if types.is_function(typ): @@ -297,7 +306,12 @@ class IODelayEstimator(algorithm.Visitor): args[arg_name] = arg_node free_vars = delay.duration.free_vars() - node.arg_exprs = { arg: self.evaluate(args[arg], abort=abort) for arg in free_vars } + node.arg_exprs = { + arg: self.evaluate(args[arg], abort=abort, + context="in the expression for argument '{}' " + "that affects I/O delay".format(arg)) + for arg in free_vars + } call_delay = delay.duration.fold(node.arg_exprs) else: assert False diff --git a/artiq/test/lit/iodelay/error_argument.py b/artiq/test/lit/iodelay/error_argument.py new file mode 100644 index 000000000..f92dd559d --- /dev/null +++ b/artiq/test/lit/iodelay/error_argument.py @@ -0,0 +1,12 @@ +# RUN: %python -m artiq.compiler.testbench.signature +diag +delay %s >%t +# RUN: OutputCheck %s --file-to-check=%t + +def f(x): + delay_mu(x) + +x = 1 + +def g(): + # CHECK-L: ${LINE:+2}: error: call cannot be interleaved because an argument cannot be statically evaluated + # CHECK-L: ${LINE:+1}: note: this expression is not supported in the expression for argument 'x' that affects I/O delay + f(x if True else x) diff --git a/artiq/test/lit/iodelay/error_arith.py b/artiq/test/lit/iodelay/error_arith.py index 54e30aa24..6a4c9f0c1 100644 --- a/artiq/test/lit/iodelay/error_arith.py +++ b/artiq/test/lit/iodelay/error_arith.py @@ -3,19 +3,19 @@ def f(a): b = 1.0 - # CHECK-L: ${LINE:+3}: error: this call cannot be interleaved + # CHECK-L: ${LINE:+3}: error: call cannot be interleaved # CHECK-L: ${LINE:+2}: note: this variable is not an argument of the innermost function # CHECK-L: ${LINE:-4}: note: only these arguments are in scope of analysis delay(b) def g(): - # CHECK-L: ${LINE:+2}: error: this call cannot be interleaved - # CHECK-L: ${LINE:+1}: note: this operator is not supported + # CHECK-L: ${LINE:+2}: error: call cannot be interleaved + # CHECK-L: ${LINE:+1}: note: this operator is not supported as an argument for delay() delay(2.0**2) def h(): - # CHECK-L: ${LINE:+2}: error: this call cannot be interleaved - # CHECK-L: ${LINE:+1}: note: this expression is not supported + # CHECK-L: ${LINE:+2}: error: call cannot be interleaved + # CHECK-L: ${LINE:+1}: note: this expression is not supported as an argument for delay_mu() delay_mu(1 if False else 2) f(1) diff --git a/artiq/test/lit/iodelay/error_builtinfn.py b/artiq/test/lit/iodelay/error_builtinfn.py index ad44c972c..b1b89a78f 100644 --- a/artiq/test/lit/iodelay/error_builtinfn.py +++ b/artiq/test/lit/iodelay/error_builtinfn.py @@ -3,12 +3,12 @@ def f(): x = 1 - # CHECK-L: ${LINE:+1}: error: this call cannot be interleaved because an argument cannot be statically evaluated + # CHECK-L: ${LINE:+1}: error: call cannot be interleaved because an argument cannot be statically evaluated delay_mu(x) def g(): x = 1.0 - # CHECK-L: ${LINE:+1}: error: this call cannot be interleaved + # CHECK-L: ${LINE:+1}: error: call cannot be interleaved delay(x) diff --git a/artiq/test/lit/iodelay/error_call_nested.py b/artiq/test/lit/iodelay/error_call_nested.py index b283c0917..2c12af9bd 100644 --- a/artiq/test/lit/iodelay/error_call_nested.py +++ b/artiq/test/lit/iodelay/error_call_nested.py @@ -2,7 +2,7 @@ # RUN: OutputCheck %s --file-to-check=%t def f(): - # CHECK-L: ${LINE:+1}: error: this call cannot be interleaved + # CHECK-L: ${LINE:+1}: error: call cannot be interleaved delay(1.0**2) def g(): diff --git a/artiq/test/lit/iodelay/error_call_subst.py b/artiq/test/lit/iodelay/error_call_subst.py index 62c6bb29a..0d1ba0843 100644 --- a/artiq/test/lit/iodelay/error_call_subst.py +++ b/artiq/test/lit/iodelay/error_call_subst.py @@ -9,5 +9,5 @@ def pulse(len): def f(): a = 100 - # CHECK-L: ${LINE:+1}: error: this call cannot be interleaved + # CHECK-L: ${LINE:+1}: error: call cannot be interleaved pulse(a) diff --git a/artiq/test/lit/iodelay/error_for.py b/artiq/test/lit/iodelay/error_for.py index 5c5b10ffc..aae5d47f3 100644 --- a/artiq/test/lit/iodelay/error_for.py +++ b/artiq/test/lit/iodelay/error_for.py @@ -3,7 +3,7 @@ def f(): r = range(10) - # CHECK-L: ${LINE:+2}: error: for statement cannot be interleaved because trip count is indeterminate + # CHECK-L: ${LINE:+2}: error: for statement cannot be interleaved because iteration count is indeterminate # CHECK-L: ${LINE:+1}: note: this value is not a constant range literal for _ in r: delay_mu(1) diff --git a/artiq/test/lit/iodelay/error_goto.py b/artiq/test/lit/iodelay/error_goto.py index 02686cd86..714db7610 100644 --- a/artiq/test/lit/iodelay/error_goto.py +++ b/artiq/test/lit/iodelay/error_goto.py @@ -4,11 +4,11 @@ def f(): for _ in range(10): delay_mu(10) - # CHECK-L: ${LINE:+1}: error: loop trip count is indeterminate because of control flow + # CHECK-L: ${LINE:+1}: error: loop iteration count is indeterminate because of control flow break def g(): for _ in range(10): delay_mu(10) - # CHECK-L: ${LINE:+1}: error: loop trip count is indeterminate because of control flow + # CHECK-L: ${LINE:+1}: error: loop iteration count is indeterminate because of control flow continue diff --git a/artiq/test/lit/iodelay/error_iterable.py b/artiq/test/lit/iodelay/error_iterable.py new file mode 100644 index 000000000..6429236db --- /dev/null +++ b/artiq/test/lit/iodelay/error_iterable.py @@ -0,0 +1,10 @@ +# RUN: %python -m artiq.compiler.testbench.signature +diag +delay %s >%t +# RUN: OutputCheck %s --file-to-check=%t + +x = 1 + +def f(): + # CHECK-L: ${LINE:+2}: error: for statement cannot be interleaved because iteration count is indeterminate + # CHECK-L: ${LINE:+1}: note: this expression is not supported in an iterable used in a for loop that is being interleaved + for _ in range(x if True else x): + delay_mu(10)