diff --git a/artiq/compiler/embedding.py b/artiq/compiler/embedding.py index fe4f41642..6680576ca 100644 --- a/artiq/compiler/embedding.py +++ b/artiq/compiler/embedding.py @@ -693,7 +693,7 @@ class Stitcher: # Let the rest of the program decide. return types.TVar() - def _quote_foreign_function(self, function, loc, syscall): + def _quote_foreign_function(self, function, loc, syscall, flags): signature = inspect.signature(function) arg_types = OrderedDict() @@ -742,7 +742,7 @@ class Stitcher: service=self.object_map.store(function)) else: function_type = types.TCFunction(arg_types, ret_type, - name=syscall) + name=syscall, flags=flags) self.functions[function] = function_type @@ -779,7 +779,8 @@ class Stitcher: # Insert a storage-less global whose type instructs the compiler # to perform a system call instead of a regular call. self._quote_foreign_function(function, loc, - syscall=function.artiq_embedded.syscall) + syscall=function.artiq_embedded.syscall, + flags=function.artiq_embedded.flags) elif function.artiq_embedded.forbidden is not None: diag = diagnostic.Diagnostic("fatal", "this function cannot be called as an RPC", {}, @@ -791,7 +792,7 @@ class Stitcher: else: # Insert a storage-less global whose type instructs the compiler # to perform an RPC instead of a regular call. - self._quote_foreign_function(function, loc, syscall=None) + self._quote_foreign_function(function, loc, syscall=None, flags=None) function_type = self.functions[function] if types.is_rpc_function(function_type): diff --git a/artiq/compiler/transforms/llvm_ir_generator.py b/artiq/compiler/transforms/llvm_ir_generator.py index 5ae0c3faa..65dd7a47d 100644 --- a/artiq/compiler/transforms/llvm_ir_generator.py +++ b/artiq/compiler/transforms/llvm_ir_generator.py @@ -184,8 +184,8 @@ class LLVMIRGenerator: self.tbaa_tree = self.llmodule.add_metadata([ ll.MetaDataString(self.llmodule, "ARTIQ TBAA") ]) - self.tbaa_noalias_call = self.llmodule.add_metadata([ - ll.MetaDataString(self.llmodule, "non-aliasing function call"), + self.tbaa_nowrite_call = self.llmodule.add_metadata([ + ll.MetaDataString(self.llmodule, "ref-only function call"), self.tbaa_tree, ll.Constant(lli64, 1) ]) @@ -401,6 +401,9 @@ class LLVMIRGenerator: llglobal = ll.Function(self.llmodule, llty, name) if name in ("__artiq_raise", "__artiq_reraise", "llvm.trap"): llglobal.attributes.add("noreturn") + if name in ("rtio_log", "send_rpc", "watchdog_set", "watchdog_clear", + self.target.print_function): + llglobal.attributes.add("nounwind") else: llglobal = ll.GlobalVariable(self.llmodule, llty, name) @@ -1117,6 +1120,8 @@ class LLVMIRGenerator: byvals = [i + 1 for i in byvals] for i in byvals: llfun.args[i].add_attribute('byval') + if 'nounwind' in insn.target_function().type.flags: + llfun.attributes.add('nounwind') return llfun, list(llargs) @@ -1282,14 +1287,14 @@ class LLVMIRGenerator: else: llcall = llresult = self.llbuilder.call(llfun, llargs, name=insn.name) + # Never add TBAA nowrite metadata to a functon with sret! + # This leads to miscompilations. + if types.is_c_function(functiontyp) and 'nowrite' in functiontyp.flags: + llcall.metadata['tbaa'] = self.tbaa_nowrite_call + if insn.is_cold: llcall.cconv = 'coldcc' - if types.is_c_function(functiontyp): - # All our global state is confined to our compilation unit, - # so by definition no FFI call can mutate it. - llcall.metadata['tbaa'] = self.tbaa_noalias_call - return llresult def process_Invoke(self, insn): diff --git a/artiq/compiler/types.py b/artiq/compiler/types.py index 878b40089..42bd5b6a7 100644 --- a/artiq/compiler/types.py +++ b/artiq/compiler/types.py @@ -342,14 +342,21 @@ class TCFunction(TFunction): A function type of a runtime-provided C function. :ivar name: (str) C function name + :ivar flags: (set of str) C function flags. + Flag ``nounwind`` means the function never raises an exception. + Flag ``nowrite`` means the function never writes any memory + that the ARTIQ Python code can observe. """ attributes = OrderedDict() - def __init__(self, args, ret, name): + def __init__(self, args, ret, name, flags={}): + for flag in flags: + assert flag in {'nounwind', 'nowrite'} super().__init__(args, OrderedDict(), ret) self.name = name self.delay = TFixedDelay(iodelay.Const(0)) + self.flags = flags def unify(self, other): if isinstance(other, TCFunction) and \ diff --git a/artiq/coredevice/cache.py b/artiq/coredevice/cache.py index 02d07d5d5..e0ca77a13 100644 --- a/artiq/coredevice/cache.py +++ b/artiq/coredevice/cache.py @@ -2,11 +2,11 @@ from artiq.language.core import * from artiq.language.types import * -@syscall +@syscall("cache_get", flags={"nounwind", "nowrite"}) def cache_get(key: TStr) -> TList(TInt32): raise NotImplementedError("syscall not simulated") -@syscall +@syscall("cache_put", flags={"nowrite"}) def cache_put(key: TStr, value: TList(TInt32)) -> TNone: raise NotImplementedError("syscall not simulated") diff --git a/artiq/coredevice/core.py b/artiq/coredevice/core.py index ecc6c17ed..c9e69f593 100644 --- a/artiq/coredevice/core.py +++ b/artiq/coredevice/core.py @@ -37,7 +37,7 @@ class CompileError(Exception): return "\n" + _render_diagnostic(self.diagnostic, colored=colors_supported) -@syscall +@syscall("rtio_get_counter", flags={"nounwind", "nowrite"}) def rtio_get_counter() -> TInt64: raise NotImplementedError("syscall not simulated") diff --git a/artiq/coredevice/dds.py b/artiq/coredevice/dds.py index 5236f2c6b..2af15175b 100644 --- a/artiq/coredevice/dds.py +++ b/artiq/coredevice/dds.py @@ -10,20 +10,20 @@ PHASE_MODE_ABSOLUTE = 1 PHASE_MODE_TRACKING = 2 -@syscall +@syscall("dds_init", flags={"nowrite"}) def dds_init(time_mu: TInt64, bus_channel: TInt32, channel: TInt32) -> TNone: raise NotImplementedError("syscall not simulated") -@syscall +@syscall("dds_set", flags={"nowrite"}) def dds_set(time_mu: TInt64, bus_channel: TInt32, channel: TInt32, ftw: TInt32, pow: TInt32, phase_mode: TInt32, amplitude: TInt32) -> TNone: raise NotImplementedError("syscall not simulated") -@syscall +@syscall("dds_batch_enter", flags={"nowrite"}) def dds_batch_enter(time_mu: TInt64) -> TNone: raise NotImplementedError("syscall not simulated") -@syscall +@syscall("dds_batch_exit", flags={"nowrite"}) def dds_batch_exit() -> TNone: raise NotImplementedError("syscall not simulated") diff --git a/artiq/coredevice/i2c.py b/artiq/coredevice/i2c.py index d834117f5..0123b21c4 100644 --- a/artiq/coredevice/i2c.py +++ b/artiq/coredevice/i2c.py @@ -3,27 +3,27 @@ from artiq.language.types import TBool, TInt32, TNone from artiq.coredevice.exceptions import I2CError -@syscall +@syscall("i2c_init", flags={"nowrite"}) def i2c_init(busno: TInt32) -> TNone: raise NotImplementedError("syscall not simulated") -@syscall +@syscall("i2c_start", flags={"nounwind", "nowrite"}) def i2c_start(busno: TInt32) -> TNone: raise NotImplementedError("syscall not simulated") -@syscall +@syscall("i2c_stop", flags={"nounwind", "nowrite"}) def i2c_stop(busno: TInt32) -> TNone: raise NotImplementedError("syscall not simulated") -@syscall +@syscall("i2c_write", flags={"nounwind", "nowrite"}) def i2c_write(busno: TInt32, b: TInt32) -> TBool: raise NotImplementedError("syscall not simulated") -@syscall +@syscall("i2c_read", flags={"nounwind", "nowrite"}) def i2c_read(busno: TInt32, ack: TBool) -> TInt32: raise NotImplementedError("syscall not simulated") diff --git a/artiq/coredevice/rtio.py b/artiq/coredevice/rtio.py index 978d80fd1..5ad71e5e3 100644 --- a/artiq/coredevice/rtio.py +++ b/artiq/coredevice/rtio.py @@ -2,17 +2,17 @@ from artiq.language.core import syscall from artiq.language.types import TInt64, TInt32, TNone -@syscall +@syscall("rtio_output", flags={"nowrite"}) def rtio_output(time_mu: TInt64, channel: TInt32, addr: TInt32, data: TInt32 ) -> TNone: raise NotImplementedError("syscall not simulated") -@syscall +@syscall("rtio_input_timestamp", flags={"nowrite"}) def rtio_input_timestamp(timeout_mu: TInt64, channel: TInt32) -> TInt64: raise NotImplementedError("syscall not simulated") -@syscall +@syscall("rtio_input_data", flags={"nowrite"}) def rtio_input_data(channel: TInt32) -> TInt32: raise NotImplementedError("syscall not simulated") diff --git a/artiq/language/core.py b/artiq/language/core.py index 209285bc3..fac508676 100644 --- a/artiq/language/core.py +++ b/artiq/language/core.py @@ -163,7 +163,7 @@ def round(value, width=32): _ARTIQEmbeddedInfo = namedtuple("_ARTIQEmbeddedInfo", - "core_name function syscall forbidden") + "core_name function syscall forbidden flags") def kernel(arg): """ @@ -192,7 +192,7 @@ def kernel(arg): return getattr(self, arg).run(run_on_core, ((self,) + k_args), k_kwargs) run_on_core.artiq_embedded = _ARTIQEmbeddedInfo( core_name=arg, function=function, syscall=None, - forbidden=False) + forbidden=False, flags={}) return run_on_core return inner_decorator else: @@ -210,10 +210,10 @@ def portable(function): """ function.artiq_embedded = \ _ARTIQEmbeddedInfo(core_name=None, function=function, syscall=None, - forbidden=False) + forbidden=False, flags={}) return function -def syscall(arg): +def syscall(arg, flags={}): """ This decorator marks a function as a system call. When executed on a core device, a C function with the provided name (or the same name as @@ -228,7 +228,8 @@ def syscall(arg): def inner_decorator(function): function.artiq_embedded = \ _ARTIQEmbeddedInfo(core_name=None, function=None, - syscall=function.__name__, forbidden=False) + syscall=function.__name__, forbidden=False, + flags=flags) return function return inner_decorator else: @@ -241,7 +242,7 @@ def host_only(function): """ function.artiq_embedded = \ _ARTIQEmbeddedInfo(core_name=None, function=None, syscall=None, - forbidden=True) + forbidden=True, flags={}) return function