diff --git a/artiq/compiler/builtins.py b/artiq/compiler/builtins.py index 2e26b8f79..4133015cc 100644 --- a/artiq/compiler/builtins.py +++ b/artiq/compiler/builtins.py @@ -138,6 +138,30 @@ def fn_print(): def fn_kernel(): return types.TBuiltinFunction("kernel") +def fn_now(): + return types.TBuiltinFunction("now") + +def fn_delay(): + return types.TBuiltinFunction("delay") + +def fn_at(): + return types.TBuiltinFunction("at") + +def fn_now_mu(): + return types.TBuiltinFunction("now_mu") + +def fn_delay_mu(): + return types.TBuiltinFunction("delay_mu") + +def fn_at_mu(): + return types.TBuiltinFunction("at_mu") + +def fn_mu_to_seconds(): + return types.TBuiltinFunction("mu_to_seconds") + +def fn_seconds_to_mu(): + return types.TBuiltinFunction("seconds_to_mu") + # Accessors def is_none(typ): diff --git a/artiq/compiler/module.py b/artiq/compiler/module.py index dbcaf2cd7..1e4232694 100644 --- a/artiq/compiler/module.py +++ b/artiq/compiler/module.py @@ -42,7 +42,7 @@ class Source: return cls(source.Buffer(f.read(), filename, 1), engine=engine) class Module: - def __init__(self, src): + def __init__(self, src, ref_period=1e-6): self.engine = src.engine self.object_map = src.object_map @@ -51,7 +51,8 @@ class Module: monomorphism_validator = validators.MonomorphismValidator(engine=self.engine) escape_validator = validators.EscapeValidator(engine=self.engine) artiq_ir_generator = transforms.ARTIQIRGenerator(engine=self.engine, - module_name=src.name) + module_name=src.name, + ref_period=ref_period) dead_code_eliminator = transforms.DeadCodeEliminator(engine=self.engine) local_access_validator = validators.LocalAccessValidator(engine=self.engine) diff --git a/artiq/compiler/prelude.py b/artiq/compiler/prelude.py index 579a474cf..2929c9c7a 100644 --- a/artiq/compiler/prelude.py +++ b/artiq/compiler/prelude.py @@ -7,17 +7,34 @@ from . import builtins def globals(): return { + # Value constructors "bool": builtins.fn_bool(), "int": builtins.fn_int(), "float": builtins.fn_float(), "list": builtins.fn_list(), "range": builtins.fn_range(), + + # Exception constructors "Exception": builtins.fn_Exception(), "IndexError": builtins.fn_IndexError(), "ValueError": builtins.fn_ValueError(), "ZeroDivisionError": builtins.fn_ZeroDivisionError(), + + # Built-in Python functions "len": builtins.fn_len(), "round": builtins.fn_round(), "print": builtins.fn_print(), + + # ARTIQ decorators "kernel": builtins.fn_kernel(), + + # ARTIQ time management functions + "now": builtins.fn_now(), + "delay": builtins.fn_delay(), + "at": builtins.fn_at(), + "now_mu": builtins.fn_now_mu(), + "delay_mu": builtins.fn_delay_mu(), + "at_mu": builtins.fn_at_mu(), + "mu_to_seconds": builtins.fn_mu_to_seconds(), + "seconds_to_mu": builtins.fn_seconds_to_mu(), } diff --git a/artiq/compiler/testbench/jit.py b/artiq/compiler/testbench/jit.py index 717bdc555..c1c90dd56 100644 --- a/artiq/compiler/testbench/jit.py +++ b/artiq/compiler/testbench/jit.py @@ -5,9 +5,9 @@ from .. import Module, Source from ..targets import NativeTarget def main(): - libartiq_personality = os.getenv('LIBARTIQ_PERSONALITY') - if libartiq_personality is not None: - llvm.load_library_permanently(libartiq_personality) + libartiq_support = os.getenv('LIBARTIQ_SUPPORT') + if libartiq_support is not None: + llvm.load_library_permanently(libartiq_support) def process_diagnostic(diag): print("\n".join(diag.render())) diff --git a/artiq/compiler/transforms/artiq_ir_generator.py b/artiq/compiler/transforms/artiq_ir_generator.py index bca40580b..84d3b07e8 100644 --- a/artiq/compiler/transforms/artiq_ir_generator.py +++ b/artiq/compiler/transforms/artiq_ir_generator.py @@ -67,10 +67,11 @@ class ARTIQIRGenerator(algorithm.Visitor): _size_type = builtins.TInt(types.TValue(32)) - def __init__(self, module_name, engine): + def __init__(self, module_name, engine, ref_period): self.engine = engine self.functions = [] self.name = [module_name] if module_name != "" else [] + self.ref_period = ir.Constant(ref_period, builtins.TFloat()) self.current_loc = None self.current_function = None self.current_class = None @@ -1409,6 +1410,39 @@ class ARTIQIRGenerator(algorithm.Visitor): self.polymorphic_print([self.visit(arg) for arg in node.args], separator=" ", suffix="\n") return ir.Constant(None, builtins.TNone()) + elif types.is_builtin(typ, "now"): + if len(node.args) == 0 and len(node.keywords) == 0: + now_mu = self.append(ir.Builtin("now_mu", [], builtins.TInt(types.TValue(64)))) + now_mu_float = self.append(ir.Coerce(now_mu, builtins.TFloat())) + return self.append(ir.Arith(ast.Mult(loc=None), now_mu_float, self.ref_period)) + else: + assert False + elif types.is_builtin(typ, "delay") or types.is_builtin(typ, "at"): + if len(node.args) == 1 and len(node.keywords) == 0: + arg = self.visit(node.args[0]) + arg_mu_float = self.append(ir.Arith(ast.Div(loc=None), arg, self.ref_period)) + arg_mu = self.append(ir.Coerce(arg_mu_float, builtins.TInt(types.TValue(64)))) + self.append(ir.Builtin(typ.name + "_mu", [arg_mu], builtins.TNone())) + else: + assert False + elif types.is_builtin(typ, "now_mu") or types.is_builtin(typ, "delay_mu") \ + or types.is_builtin(typ, "at_mu"): + return self.append(ir.Builtin(typ.name, + [self.visit(arg) for arg in node.args], node.type)) + elif types.is_builtin(typ, "mu_to_seconds"): + if len(node.args) == 1 and len(node.keywords) == 0: + arg = self.visit(node.args[0]) + arg_float = self.append(ir.Coerce(arg, builtins.TFloat())) + return self.append(ir.Arith(ast.Mult(loc=None), arg_float, self.ref_period)) + else: + assert False + elif types.is_builtin(typ, "seconds_to_mu"): + if len(node.args) == 1 and len(node.keywords) == 0: + arg = self.visit(node.args[0]) + arg_mu = self.append(ir.Arith(ast.Div(loc=None), arg, self.ref_period)) + return self.append(ir.Coerce(arg_mu, builtins.TInt(types.TValue(64)))) + else: + assert False elif types.is_exn_constructor(typ): return self.alloc_exn(node.type, *[self.visit(arg_node) for arg_node in node.args]) elif types.is_constructor(typ): diff --git a/artiq/compiler/transforms/inferencer.py b/artiq/compiler/transforms/inferencer.py index 852617c5e..d82c32cd0 100644 --- a/artiq/compiler/transforms/inferencer.py +++ b/artiq/compiler/transforms/inferencer.py @@ -505,6 +505,17 @@ class Inferencer(algorithm.Visitor): node.func.loc, notes=valid_forms) self.engine.process(diag) + def simple_form(info, arg_types=[], return_type=builtins.TNone()): + self._unify(node.type, return_type, + node.loc, None) + + if len(node.args) == len(arg_types) and len(node.keywords) == 0: + for index, arg_type in enumerate(arg_types): + self._unify(node.args[index].type, arg_type, + node.args[index].loc, None) + else: + diagnose([ valid_form(info) ]) + if types.is_exn_constructor(typ): valid_forms = lambda: [ valid_form("{exn}() -> {exn}".format(exn=typ.name)), @@ -730,6 +741,30 @@ class Inferencer(algorithm.Visitor): pass else: diagnose(valid_forms()) + elif types.is_builtin(typ, "now"): + simple_form("now() -> float", + [], builtins.TFloat()) + elif types.is_builtin(typ, "delay"): + simple_form("delay(time:float) -> None", + [builtins.TFloat()]) + elif types.is_builtin(typ, "at"): + simple_form("at(time:float) -> None", + [builtins.TFloat()]) + elif types.is_builtin(typ, "now_mu"): + simple_form("now_mu() -> int(width=64)", + [], builtins.TInt(types.TValue(64))) + elif types.is_builtin(typ, "delay_mu"): + simple_form("delay_mu(time_mu:int(width=64)) -> None", + [builtins.TInt(types.TValue(64))]) + elif types.is_builtin(typ, "at_mu"): + simple_form("at_mu(time_mu:int(width=64)) -> None", + [builtins.TInt(types.TValue(64))]) + elif types.is_builtin(typ, "mu_to_seconds"): + simple_form("mu_to_seconds(time_mu:int(width=64)) -> float", + [builtins.TInt(types.TValue(64))], builtins.TFloat()) + elif types.is_builtin(typ, "seconds_to_mu"): + simple_form("seconds_to_mu(time:float) -> int(width=64)", + [builtins.TFloat()], builtins.TInt(types.TValue(64))) elif types.is_constructor(typ): # An user-defined class. self._unify(node.type, typ.find().instance, diff --git a/artiq/compiler/transforms/llvm_ir_generator.py b/artiq/compiler/transforms/llvm_ir_generator.py index ef20056f0..08408c2d6 100644 --- a/artiq/compiler/transforms/llvm_ir_generator.py +++ b/artiq/compiler/transforms/llvm_ir_generator.py @@ -14,6 +14,7 @@ llvoid = ll.VoidType() lli1 = ll.IntType(1) lli8 = ll.IntType(8) lli32 = ll.IntType(32) +lli64 = ll.IntType(64) lldouble = ll.DoubleType() llptr = ll.IntType(8).as_pointer() llmetadata = ll.MetaData() @@ -331,9 +332,9 @@ class LLVMIRGenerator: assert False def llbuiltin(self, name): - llfun = self.llmodule.get_global(name) - if llfun is not None: - return llfun + llglobal = self.llmodule.get_global(name) + if llglobal is not None: + return llglobal if name in "llvm.donothing": llty = ll.FunctionType(llvoid, []) @@ -366,13 +367,19 @@ class LLVMIRGenerator: var_arg=True) elif name == "recv_rpc": llty = ll.FunctionType(lli32, [llptr]) + elif name == "now": + llty = lli64 else: assert False - llfun = ll.Function(self.llmodule, llty, name) - if name in ("__artiq_raise", "__artiq_reraise", "llvm.trap"): - llfun.attributes.add("noreturn") - return llfun + if isinstance(llty, ll.FunctionType): + llglobal = ll.Function(self.llmodule, llty, name) + if name in ("__artiq_raise", "__artiq_reraise", "llvm.trap"): + llglobal.attributes.add("noreturn") + else: + llglobal = ll.GlobalVariable(self.llmodule, llty, name) + + return llglobal def map(self, value): if isinstance(value, (ir.Argument, ir.Instruction, ir.BasicBlock)): @@ -774,6 +781,17 @@ class LLVMIRGenerator: elif insn.op == "exncast": # This is an identity cast at LLVM IR level. return self.map(insn.operands[0]) + elif insn.op == "now_mu": + return self.llbuilder.load(self.llbuiltin("now"), name=insn.name) + elif insn.op == "delay_mu": + interval, = insn.operands + llnowptr = self.llbuiltin("now") + llnow = self.llbuilder.load(llnowptr) + lladjusted = self.llbuilder.add(llnow, self.map(interval)) + return self.llbuilder.store(lladjusted, llnowptr) + elif insn.op == "at_mu": + time, = insn.operands + return self.llbuilder.store(self.map(time), self.llbuiltin("now")) else: assert False diff --git a/artiq/coredevice/core.py b/artiq/coredevice/core.py index 18cc981c3..afced23bf 100644 --- a/artiq/coredevice/core.py +++ b/artiq/coredevice/core.py @@ -39,7 +39,7 @@ class Core: stitcher.stitch_call(function, args, kwargs) stitcher.finalize() - module = Module(stitcher) + module = Module(stitcher, ref_period=self.ref_period) target = OR1KTarget() library = target.compile_and_link([module]) diff --git a/artiq/py2llvm_old/transforms/lower_time.py b/artiq/py2llvm_old/transforms/lower_time.py deleted file mode 100644 index 5b3df0245..000000000 --- a/artiq/py2llvm_old/transforms/lower_time.py +++ /dev/null @@ -1,64 +0,0 @@ -""" -This transform implements time management functions (delay_mu/now_mu/at_mu) -using an accumulator 'now' and simple replacement rules: - - delay_mu(t) -> now += t - now_mu() -> now - at_mu(t) -> now = t - -The function delay(), that uses seconds, must be lowered to delay_mu() before -invoking this transform. -The accumulator is initialized to an int64 value at the beginning of the -output function. -""" - -import ast - - -class _TimeLowerer(ast.NodeTransformer): - def visit_Call(self, node): - if node.func.id == "now_mu": - return ast.copy_location(ast.Name("now", ast.Load()), node) - else: - self.generic_visit(node) - return node - - def visit_Expr(self, node): - r = node - if isinstance(node.value, ast.Call): - funcname = node.value.func.id - if funcname == "delay_mu": - r = ast.copy_location( - ast.AugAssign(target=ast.Name("now", ast.Store()), - op=ast.Add(), - value=node.value.args[0]), - node) - elif funcname == "at_mu": - r = ast.copy_location( - ast.Assign(targets=[ast.Name("now", ast.Store())], - value=node.value.args[0]), - node) - self.generic_visit(r) - return r - - -def lower_time(func_def): - _TimeLowerer().visit(func_def) - call_init = ast.Call( - func=ast.Name("syscall", ast.Load()), - args=[ast.Str("now_init")], - keywords=[], starargs=None, kwargs=None) - stmt_init = ast.Assign(targets=[ast.Name("now", ast.Store())], - value=call_init) - call_save = ast.Call( - func=ast.Name("syscall", ast.Load()), - args=[ast.Str("now_save"), ast.Name("now", ast.Load())], - keywords=[], starargs=None, kwargs=None) - stmt_save = ast.Expr(call_save) - func_def.body = [ - stmt_init, - ast.Try(body=func_def.body, - handlers=[], - orelse=[], - finalbody=[stmt_save]) - ] diff --git a/artiq/py2llvm_old/transforms/quantize_time.py b/artiq/py2llvm_old/transforms/quantize_time.py index aad75069a..42e04f564 100644 --- a/artiq/py2llvm_old/transforms/quantize_time.py +++ b/artiq/py2llvm_old/transforms/quantize_time.py @@ -1,69 +1,3 @@ -""" -This transform turns calls to delay() that use non-integer time -expressed in seconds into calls to delay_mu() that use int64 time -expressed in multiples of ref_period. - -It does so by inserting multiplication/division/rounding operations around -those calls. - -The seconds_to_mu and mu_to_seconds core language functions are also -implemented here, as well as watchdog to syscall conversion. -""" - -import ast - -from artiq.transforms.tools import value_to_ast - - -def _seconds_to_mu(ref_period, node): - divided = ast.copy_location( - ast.BinOp(left=node, - op=ast.Div(), - right=value_to_ast(ref_period)), - node) - return ast.copy_location( - ast.Call(func=ast.Name("round64", ast.Load()), - args=[divided], - keywords=[], starargs=[], kwargs=[]), - divided) - - -def _mu_to_seconds(ref_period, node): - return ast.copy_location( - ast.BinOp(left=node, - op=ast.Mult(), - right=value_to_ast(ref_period)), - node) - - -class _TimeQuantizer(ast.NodeTransformer): - def __init__(self, ref_period): - self.ref_period = ref_period - self.watchdog_id_counter = 0 - - def visit_Call(self, node): - funcname = node.func.id - if funcname == "delay": - node.func.id = "delay_mu" - if (isinstance(node.args[0], ast.Call) - and node.args[0].func.id == "mu_to_seconds"): - # optimize: - # delay(mu_to_seconds(x)) -> delay_mu(x) - node.args[0] = self.visit(node.args[0].args[0]) - else: - node.args[0] = _seconds_to_mu(self.ref_period, - self.visit(node.args[0])) - return node - elif funcname == "seconds_to_mu": - return _seconds_to_mu(self.ref_period, - self.visit(node.args[0])) - elif funcname == "mu_to_seconds": - return _mu_to_seconds(self.ref_period, - self.visit(node.args[0])) - else: - self.generic_visit(node) - return node - def visit_With(self, node): self.generic_visit(node) if (isinstance(node.items[0].context_expr, ast.Call) @@ -107,7 +41,3 @@ class _TimeQuantizer(ast.NodeTransformer): finalbody=[stmt_clear]) ] return node - - -def quantize_time(func_def, ref_period): - _TimeQuantizer(ref_period).visit(func_def) diff --git a/lit-test/libartiq_personality/Makefile b/lit-test/libartiq_support/Makefile similarity index 50% rename from lit-test/libartiq_personality/Makefile rename to lit-test/libartiq_support/Makefile index 2a72a7185..bac7a11c9 100644 --- a/lit-test/libartiq_personality/Makefile +++ b/lit-test/libartiq_support/Makefile @@ -1,4 +1,4 @@ CC ?= clang -libartiq_personality.so: ../../soc/runtime/artiq_personality.c artiq_terminate.c +libartiq_support.so: ../../soc/runtime/artiq_personality.c artiq_terminate.c artiq_time.c $(CC) -std=c99 -Wall -Werror -I. -I../../soc/runtime -g -fPIC -shared -o $@ $^ diff --git a/lit-test/libartiq_personality/__cxxabi_config.h b/lit-test/libartiq_support/__cxxabi_config.h similarity index 100% rename from lit-test/libartiq_personality/__cxxabi_config.h rename to lit-test/libartiq_support/__cxxabi_config.h diff --git a/lit-test/libartiq_personality/artiq_terminate.c b/lit-test/libartiq_support/artiq_terminate.c similarity index 100% rename from lit-test/libartiq_personality/artiq_terminate.c rename to lit-test/libartiq_support/artiq_terminate.c diff --git a/lit-test/libartiq_support/artiq_time.c b/lit-test/libartiq_support/artiq_time.c new file mode 100644 index 000000000..1afeadbc0 --- /dev/null +++ b/lit-test/libartiq_support/artiq_time.c @@ -0,0 +1,3 @@ +#include + +int64_t now = 0; diff --git a/lit-test/libartiq_support/libartiq_personality.so b/lit-test/libartiq_support/libartiq_personality.so new file mode 100755 index 000000000..80ff44cb8 Binary files /dev/null and b/lit-test/libartiq_support/libartiq_personality.so differ diff --git a/lit-test/libartiq_support/libartiq_support.so b/lit-test/libartiq_support/libartiq_support.so new file mode 100755 index 000000000..baa5a31ae Binary files /dev/null and b/lit-test/libartiq_support/libartiq_support.so differ diff --git a/lit-test/libartiq_personality/unwind.h b/lit-test/libartiq_support/unwind.h similarity index 100% rename from lit-test/libartiq_personality/unwind.h rename to lit-test/libartiq_support/unwind.h diff --git a/lit-test/test/lit.cfg b/lit-test/test/lit.cfg index 9cbc6d070..35da31b04 100644 --- a/lit-test/test/lit.cfg +++ b/lit-test/test/lit.cfg @@ -17,11 +17,11 @@ not_ = '{} {}'.format(python_executable, os.path.join(root, 'not.py')) config.substitutions.append( ('%not', not_) ) if os.name == 'posix': - personality_build = os.path.join(root, 'libartiq_personality') - if subprocess.call(['make', '-sC', personality_build]) != 0: + support_build = os.path.join(root, 'libartiq_support') + if subprocess.call(['make', '-sC', support_build]) != 0: lit_config.fatal("Unable to build JIT support library") - personality_lib = os.path.join(personality_build, 'libartiq_personality.so') - config.environment['LIBARTIQ_PERSONALITY'] = personality_lib + support_lib = os.path.join(support_build, 'libartiq_support.so') + config.environment['LIBARTIQ_SUPPORT'] = support_lib config.available_features.add('exceptions') diff --git a/lit-test/test/time/advance.py b/lit-test/test/time/advance.py new file mode 100644 index 000000000..0faaaa597 --- /dev/null +++ b/lit-test/test/time/advance.py @@ -0,0 +1,9 @@ +# RUN: %python -m artiq.compiler.testbench.jit %s + +assert now() == 0.0 +delay(100.0) +assert now() == 100.0 +at(12345.0) +assert now() == 12345.0 + +assert now_mu() == 12345000000 diff --git a/lit-test/test/time/advance_mu.py b/lit-test/test/time/advance_mu.py new file mode 100644 index 000000000..0ff97e1d1 --- /dev/null +++ b/lit-test/test/time/advance_mu.py @@ -0,0 +1,7 @@ +# RUN: %python -m artiq.compiler.testbench.jit %s + +assert now_mu() == 0 +delay_mu(100) +assert now_mu() == 100 +at_mu(12345) +assert now_mu() == 12345 diff --git a/lit-test/test/time/conversion.py b/lit-test/test/time/conversion.py new file mode 100644 index 000000000..ccabb41cb --- /dev/null +++ b/lit-test/test/time/conversion.py @@ -0,0 +1,4 @@ +# RUN: %python -m artiq.compiler.testbench.jit %s + +assert seconds_to_mu(2.0) == 2000000 +assert mu_to_seconds(1500000) == 1.5 diff --git a/soc/runtime/ksupport.c b/soc/runtime/ksupport.c index c059ea42c..244dcb7c0 100644 --- a/soc/runtime/ksupport.c +++ b/soc/runtime/ksupport.c @@ -19,6 +19,8 @@ void ksupport_abort(void); +int64_t now; + /* compiler-rt symbols */ extern void __divsi3, __modsi3, __ledf2, __gedf2, __unorddf2, __eqdf2, __ltdf2, __nedf2, __gtdf2, __negsf2, __negdf2, __addsf3, __subsf3, __mulsf3, @@ -87,8 +89,7 @@ static const struct symbol runtime_exports[] = { {"abort", &ksupport_abort}, /* proxified syscalls */ - {"now_init", &now_init}, - {"now_save", &now_save}, + {"now", &now}, {"watchdog_set", &watchdog_set}, {"watchdog_clear", &watchdog_clear}, @@ -212,7 +213,10 @@ int main(void) void (*kernel_init)() = request->library_info->init; mailbox_send_and_wait(&load_reply); + + now = now_init(); kernel_init(); + now_save(now); struct msg_base finished_reply; finished_reply.type = MESSAGE_TYPE_FINISHED;