diff --git a/artiq/compiler/builtins.py b/artiq/compiler/builtins.py index 57d506b78..9ae3e22b1 100644 --- a/artiq/compiler/builtins.py +++ b/artiq/compiler/builtins.py @@ -150,6 +150,9 @@ def obj_parallel(): def obj_sequential(): return types.TBuiltin("sequential") +def fn_watchdog(): + return types.TBuiltinFunction("watchdog") + def fn_now(): return types.TBuiltinFunction("now") diff --git a/artiq/compiler/prelude.py b/artiq/compiler/prelude.py index 34ecd9695..4c616a3c4 100644 --- a/artiq/compiler/prelude.py +++ b/artiq/compiler/prelude.py @@ -31,6 +31,7 @@ def globals(): # ARTIQ context managers "parallel": builtins.obj_parallel(), "sequential": builtins.obj_sequential(), + "watchdog": builtins.fn_watchdog(), # ARTIQ time management functions "now": builtins.fn_now(), diff --git a/artiq/compiler/transforms/artiq_ir_generator.py b/artiq/compiler/transforms/artiq_ir_generator.py index 5053444f3..55f6bfadb 100644 --- a/artiq/compiler/transforms/artiq_ir_generator.py +++ b/artiq/compiler/transforms/artiq_ir_generator.py @@ -714,6 +714,32 @@ class ARTIQIRGenerator(algorithm.Visitor): for tail in tails: if not tail.is_terminated(): tail.append(ir.Branch(self.current_block)) + elif isinstance(context_expr_node, asttyped.CallT) and \ + types.is_builtin(context_expr_node.func.type, "watchdog"): + timeout = self.visit(context_expr_node.args[0]) + timeout_ms = self.append(ir.Arith(ast.Mult(loc=None), timeout, + ir.Constant(1000, builtins.TFloat()))) + timeout_ms_int = self.append(ir.Coerce(timeout_ms, builtins.TInt32())) + + watchdog = self.append(ir.Builtin("watchdog_set", [timeout_ms_int], builtins.TInt32())) + + dispatcher = self.add_block("watchdog.dispatch") + + try: + old_unwind, self.unwind_target = self.unwind_target, dispatcher + self.visit(node.body) + finally: + self.unwind_target = old_unwind + + cleanup = self.add_block('watchdog.cleanup') + landingpad = dispatcher.append(ir.LandingPad(cleanup)) + cleanup.append(ir.Builtin("watchdog_clear", [watchdog], builtins.TNone())) + cleanup.append(ir.Reraise(self.unwind_target)) + + if not self.current_block.is_terminated(): + self.append(ir.Builtin("watchdog_clear", [watchdog], builtins.TNone())) + else: + assert False # Expression visitors # These visitors return a node in addition to mutating @@ -1551,7 +1577,11 @@ class ARTIQIRGenerator(algorithm.Visitor): elif types.is_constructor(typ): return self.append(ir.Alloc([], typ.instance)) else: - assert False + diag = diagnostic.Diagnostic("error", + "builtin function '{name}' cannot be used in this context", + {"name": typ.name}, + node.loc) + self.engine.process(diag) def visit_CallT(self, node): typ = node.func.type.find() diff --git a/artiq/compiler/transforms/inferencer.py b/artiq/compiler/transforms/inferencer.py index 95d7f3fc6..b9bb7554e 100644 --- a/artiq/compiler/transforms/inferencer.py +++ b/artiq/compiler/transforms/inferencer.py @@ -757,6 +757,9 @@ class Inferencer(algorithm.Visitor): elif types.is_builtin(typ, "seconds_to_mu"): simple_form("seconds_to_mu(time:float) -> int(width=64)", [builtins.TFloat()], builtins.TInt64()) + elif types.is_builtin(typ, "watchdog"): + simple_form("watchdog(time:float) -> [builtin context manager]", + [builtins.TFloat()], builtins.TNone()) elif types.is_constructor(typ): # An user-defined class. self._unify(node.type, typ.find().instance, @@ -942,7 +945,10 @@ class Inferencer(algorithm.Visitor): self.generic_visit(node) typ = node.context_expr.type - if not (types.is_builtin(typ, "parallel") or types.is_builtin(typ, "sequential")): + if not (types.is_builtin(typ, "parallel") or + types.is_builtin(typ, "sequential") or + (isinstance(node.context_expr, asttyped.CallT) and + types.is_builtin(node.context_expr.func.type, "watchdog"))): diag = diagnostic.Diagnostic("error", "value of type {type} cannot act as a context manager", {"type": types.TypePrinter().name(typ)}, diff --git a/artiq/compiler/transforms/llvm_ir_generator.py b/artiq/compiler/transforms/llvm_ir_generator.py index e85472d22..f057c8b53 100644 --- a/artiq/compiler/transforms/llvm_ir_generator.py +++ b/artiq/compiler/transforms/llvm_ir_generator.py @@ -369,6 +369,10 @@ class LLVMIRGenerator: llty = ll.FunctionType(lli32, [llptr]) elif name == "now": llty = lli64 + elif name == "watchdog_set": + llty = ll.FunctionType(lli32, [lli32]) + elif name == "watchdog_clear": + llty = ll.FunctionType(llvoid, [lli32]) else: assert False @@ -793,6 +797,12 @@ class LLVMIRGenerator: llnow = self.llbuilder.load(llnowptr) lladjusted = self.llbuilder.add(llnow, self.map(interval)) return self.llbuilder.store(lladjusted, llnowptr) + elif insn.op == "watchdog_set": + interval, = insn.operands + return self.llbuilder.call(self.llbuiltin("watchdog_set"), [self.map(interval)]) + elif insn.op == "watchdog_clear": + id, = insn.operands + return self.llbuilder.call(self.llbuiltin("watchdog_clear"), [self.map(id)]) else: assert False diff --git a/lit-test/libartiq_support/artiq_time.c b/lit-test/libartiq_support/artiq_time.c index 1afeadbc0..f60e46e47 100644 --- a/lit-test/libartiq_support/artiq_time.c +++ b/lit-test/libartiq_support/artiq_time.c @@ -1,3 +1,15 @@ #include +#include int64_t now = 0; + +int watchdog_set(int ms) +{ + printf("watchdog_set %d\n", ms); + return ms; +} + +void watchdog_clear(int id) +{ + printf("watchdog_clear %d\n", id); +} diff --git a/lit-test/test/codegen/error_illegal_builtin.py b/lit-test/test/codegen/error_illegal_builtin.py new file mode 100644 index 000000000..ae75ad9b7 --- /dev/null +++ b/lit-test/test/codegen/error_illegal_builtin.py @@ -0,0 +1,5 @@ +# RUN: %python -m artiq.compiler.testbench.signature +diag %s >%t +# RUN: OutputCheck %s --file-to-check=%t + +# CHECK-L: ${LINE:+1}: error: builtin function 'watchdog' cannot be used in this context +watchdog(1.0) diff --git a/lit-test/test/time/watchdog.py b/lit-test/test/time/watchdog.py new file mode 100644 index 000000000..b7df4b509 --- /dev/null +++ b/lit-test/test/time/watchdog.py @@ -0,0 +1,35 @@ +# RUN: %python -m artiq.compiler.testbench.jit %s >%t +# RUN: OutputCheck %s --file-to-check=%t + +def f(): + with watchdog(1.0): + pass + +def g(): + with watchdog(2.0): + raise Exception() + +def h(): + try: + g() + except: + pass + +def i(): + try: + with watchdog(3.0): + raise Exception() + except: + pass + +# CHECK-L: watchdog_set 1000 +# CHECK-L: watchdog_clear 1000 +f() + +# CHECK-L: watchdog_set 2000 +# CHECK-L: watchdog_clear 2000 +h() + +# CHECK-L: watchdog_set 3000 +# CHECK-L: watchdog_clear 3000 +i()