Implement syscalls for the new compiler.

This commit is contained in:
whitequark 2015-08-10 19:25:48 +03:00
parent 435559fe50
commit c72267ecf5
8 changed files with 223 additions and 225 deletions

View File

@ -238,6 +238,10 @@ class Stitcher:
name = function.__code__.co_name
source_line = linecache.getline(filename, line)
while source_line.lstrip().startswith("@"):
line += 1
source_line = linecache.getline(filename, line)
column = re.search("def", source_line).start(0)
source_buffer = source.Buffer(source_line, filename, line)
return source.Range(source_buffer, column, column)
@ -248,11 +252,16 @@ class Stitcher:
{"function": function.__name__},
self._function_loc(function))
def _extract_annot(self, function, annot, kind, call_loc):
def _extract_annot(self, function, annot, kind, call_loc, is_syscall):
if not isinstance(annot, types.Type):
note = diagnostic.Diagnostic("note",
"in function called remotely here", {},
call_loc)
if is_syscall:
note = diagnostic.Diagnostic("note",
"in system call here", {},
call_loc)
else:
note = diagnostic.Diagnostic("note",
"in function called remotely here", {},
call_loc)
diag = diagnostic.Diagnostic("error",
"type annotation for {kind}, '{annot}', is not an ARTIQ type",
{"kind": kind, "annot": repr(annot)},
@ -264,11 +273,19 @@ class Stitcher:
else:
return annot
def _type_of_param(self, function, loc, param):
def _type_of_param(self, function, loc, param, is_syscall):
if param.annotation is not inspect.Parameter.empty:
# Type specified explicitly.
return self._extract_annot(function, param.annotation,
"argument {}".format(param.name), loc)
"argument '{}'".format(param.name), loc,
is_syscall)
elif is_syscall:
# Syscalls must be entirely annotated.
diag = diagnostic.Diagnostic("error",
"system call argument '{argument}' must have a type annotation",
{"argument": param.name},
self._function_loc(function))
self.engine.process(diag)
elif param.default is not inspect.Parameter.empty:
# Try and infer the type from the default value.
# This is tricky, because the default value might not have
@ -281,8 +298,8 @@ class Stitcher:
def proxy_diagnostic(diag):
note = diagnostic.Diagnostic("note",
"expanded from here while trying to infer a type for an"
" unannotated optional argument '{param_name}' from its default value",
{"param_name": param.name},
" unannotated optional argument '{argument}' from its default value",
{"argument": param.name},
loc)
diag.notes.append(note)
@ -300,7 +317,7 @@ class Stitcher:
# Let the rest of the program decide.
return types.TVar()
def _quote_rpc_function(self, function, loc):
def _quote_foreign_function(self, function, loc, syscall):
signature = inspect.signature(function)
arg_types = OrderedDict()
@ -318,44 +335,73 @@ class Stitcher:
continue
if param.default is inspect.Parameter.empty:
arg_types[param.name] = self._type_of_param(function, loc, param)
arg_types[param.name] = self._type_of_param(function, loc, param,
is_syscall=syscall is not None)
elif syscall is None:
optarg_types[param.name] = self._type_of_param(function, loc, param,
is_syscall=syscall is not None)
else:
optarg_types[param.name] = self._type_of_param(function, loc, param)
diag = diagnostic.Diagnostic("error",
"system call argument '{argument}' must not have a default value",
{"argument": param.name},
self._function_loc(function))
self.engine.process(diag)
if signature.return_annotation is not inspect.Signature.empty:
ret_type = self._extract_annot(function, signature.return_annotation,
"return type", loc)
else:
diag = diagnostic.Diagnostic("fatal",
"function must have a return type specified to be called remotely", {},
"return type", loc, is_syscall=syscall is not None)
elif syscall is None:
diag = diagnostic.Diagnostic("error",
"function must have a return type annotation to be called remotely", {},
self._function_loc(function))
self.engine.process(diag)
ret_type = types.TVar()
else: # syscall is not None
diag = diagnostic.Diagnostic("error",
"system call must have a return type annotation", {},
self._function_loc(function))
self.engine.process(diag)
ret_type = types.TVar()
rpc_type = types.TRPCFunction(arg_types, optarg_types, ret_type,
service=self._map(function))
if syscall is None:
function_type = types.TRPCFunction(arg_types, optarg_types, ret_type,
service=self._map(function))
function_name = "__rpc_{}__".format(function_type.service)
else:
function_type = types.TCFunction(arg_types, ret_type,
name=syscall)
function_name = "__ffi_{}__".format(function_type.name)
rpc_name = "__rpc_{}__".format(rpc_type.service)
self.globals[rpc_name] = rpc_type
self.functions[function] = rpc_name
self.globals[function_name] = function_type
self.functions[function] = function_name
return rpc_name
return function_name
def _quote_function(self, function, loc):
if function in self.functions:
return self.functions[function]
if hasattr(function, "artiq_embedded"):
# Insert the typed AST for the new function and restart inference.
# It doesn't really matter where we insert as long as it is before
# the final call.
function_node = self._quote_embedded_function(function)
self.typedtree.insert(0, function_node)
self.inference_finished = False
return function_node.name
if function.artiq_embedded.function is not None:
# Insert the typed AST for the new function and restart inference.
# It doesn't really matter where we insert as long as it is before
# the final call.
function_node = self._quote_embedded_function(function)
self.typedtree.insert(0, function_node)
self.inference_finished = False
return function_node.name
elif function.artiq_embedded.syscall is not None:
# Insert a storage-less global whose type instructs the compiler
# to perform a system call instead of a regular call.
return self._quote_foreign_function(function, loc,
syscall=function.artiq_embedded.syscall)
else:
assert False
else:
# Insert a storage-less global whose type instructs the compiler
# to perform an RPC instead of a regular call.
return self._quote_rpc_function(function, loc)
return self._quote_foreign_function(function, loc,
syscall=None)
def stitch_call(self, function, args, kwargs):
function_node = self._quote_embedded_function(function)

View File

@ -175,7 +175,7 @@ class LLVMIRGenerator:
typ = typ.find()
if types.is_tuple(typ):
return ll.LiteralStructType([self.llty_of_type(eltty) for eltty in typ.elts])
elif types.is_rpc_function(typ):
elif types.is_rpc_function(typ) or types.is_c_function(typ):
if for_return:
return llvoid
else:
@ -731,11 +731,20 @@ class LLVMIRGenerator:
return llvalue
def _prepare_closure_call(self, insn):
llclosure, llargs = self.map(insn.target_function()), map(self.map, insn.arguments())
llenv = self.llbuilder.extract_value(llclosure, 0)
llfun = self.llbuilder.extract_value(llclosure, 1)
llclosure = self.map(insn.target_function())
llargs = [self.map(arg) for arg in insn.arguments()]
llenv = self.llbuilder.extract_value(llclosure, 0)
llfun = self.llbuilder.extract_value(llclosure, 1)
return llfun, [llenv] + list(llargs)
def _prepare_ffi_call(self, insn):
llargs = [self.map(arg) for arg in insn.arguments()]
llfunty = ll.FunctionType(self.llty_of_type(insn.type, for_return=True),
[llarg.type for llarg in llargs])
llfun = ll.Function(self.llmodule, llfunty,
insn.target_function().type.name)
return llfun, list(llargs)
# See session.c:{send,receive}_rpc_value and comm_generic.py:_{send,receive}_rpc_value.
def _rpc_tag(self, typ, error_handler):
if types.is_tuple(typ):
@ -869,6 +878,10 @@ class LLVMIRGenerator:
insn.target_function().type,
insn.arguments(),
llnormalblock=None, llunwindblock=None)
elif types.is_c_function(insn.target_function().type):
llfun, llargs = self._prepare_ffi_call(insn)
return self.llbuilder.call(llfun, llargs,
name=insn.name)
else:
llfun, llargs = self._prepare_closure_call(insn)
return self.llbuilder.call(llfun, llargs,
@ -882,6 +895,10 @@ class LLVMIRGenerator:
insn.target_function().type,
insn.arguments(),
llnormalblock, llunwindblock)
elif types.is_c_function(insn.target_function().type):
llfun, llargs = self._prepare_ffi_call(insn)
return self.llbuilder.invoke(llfun, llargs, llnormalblock, llunwindblock,
name=insn.name)
else:
llfun, llargs = self._prepare_closure_call(insn)
return self.llbuilder.invoke(llfun, llargs, llnormalblock, llunwindblock,

View File

@ -258,6 +258,26 @@ class TRPCFunction(TFunction):
else:
raise UnificationError(self, other)
class TCFunction(TFunction):
"""
A function type of a runtime-provided C function.
:ivar name: (str) C function name
"""
def __init__(self, args, ret, name):
super().__init__(args, OrderedDict(), ret)
self.name = name
def unify(self, other):
if isinstance(other, TCFunction) and \
self.name == other.name:
super().unify(other)
elif isinstance(other, TVar):
other.unify(self)
else:
raise UnificationError(self, other)
class TBuiltin(Type):
"""
An instance of builtin type. Every instance of a builtin
@ -371,6 +391,9 @@ def is_function(typ):
def is_rpc_function(typ):
return isinstance(typ.find(), TRPCFunction)
def is_c_function(typ):
return isinstance(typ.find(), TCFunction)
def is_builtin(typ, name=None):
typ = typ.find()
if name is None:
@ -423,7 +446,7 @@ class TypePrinter(object):
return "(%s,)" % self.name(typ.elts[0])
else:
return "(%s)" % ", ".join(list(map(self.name, typ.elts)))
elif isinstance(typ, (TFunction, TRPCFunction)):
elif isinstance(typ, (TFunction, TRPCFunction, TCFunction)):
args = []
args += [ "%s:%s" % (arg, self.name(typ.args[arg])) for arg in typ.args]
args += ["?%s:%s" % (arg, self.name(typ.optargs[arg])) for arg in typ.optargs]
@ -431,6 +454,8 @@ class TypePrinter(object):
if isinstance(typ, TRPCFunction):
return "rpc({}) {}".format(typ.service, signature)
if isinstance(typ, TCFunction):
return "ffi({}) {}".format(repr(typ.name), signature)
elif isinstance(typ, TFunction):
return signature
elif isinstance(typ, TBuiltinFunction):

View File

@ -1,9 +1,9 @@
import sys, tempfile
import sys
from pythonparser import diagnostic
from artiq.language.core import *
from artiq.language.units import ns
from artiq.language.types import *
from artiq.compiler import Stitcher, Module
from artiq.compiler.targets import OR1KTarget
@ -15,6 +15,11 @@ from artiq.coredevice import exceptions
class CompileError(Exception):
pass
@syscall
def rtio_get_counter() -> TInt64:
raise NotImplementedError("syscall not simulated")
class Core:
def __init__(self, dmgr, ref_period=8*ns, external_clock=False):
self.comm = dmgr.get("comm")
@ -58,8 +63,8 @@ class Core:
@kernel
def get_rtio_counter_mu(self):
return syscall("rtio_get_counter")
return rtio_get_counter()
@kernel
def break_realtime(self):
at_mu(syscall("rtio_get_counter") + 125000)
at_mu(rtio_get_counter() + 125000)

View File

@ -9,6 +9,24 @@ PHASE_MODE_ABSOLUTE = 1
PHASE_MODE_TRACKING = 2
@syscall
def dds_init(time_mu: TInt64, channel: TInt32) -> TNone:
raise NotImplementedError("syscall not simulated")
@syscall
def dds_batch_enter(time_mu: TInt64) -> TNone:
raise NotImplementedError("syscall not simulated")
@syscall
def dds_batch_exit() -> TNone:
raise NotImplementedError("syscall not simulated")
@syscall
def dds_set(time_mu: TInt64, channel: TInt32, ftw: TInt32,
pow: TInt32, phase_mode: TInt32) -> TNone:
raise NotImplementedError("syscall not simulated")
class _BatchContextManager:
def __init__(self, dds_bus):
self.dds_bus = dds_bus
@ -34,13 +52,13 @@ class DDSBus:
def batch_enter(self):
"""Starts a DDS command batch. All DDS commands are buffered
after this call, until ``batch_exit`` is called."""
syscall("dds_batch_enter", now_mu())
dds_batch_enter(now_mu())
@kernel
def batch_exit(self):
"""Ends a DDS command batch. All buffered DDS commands are issued
on the bus, and FUD is pulsed at the time the batch started."""
syscall("dds_batch_exit")
dds_batch_exit()
class _DDSGeneric:
@ -91,7 +109,7 @@ class _DDSGeneric:
"""Resets and initializes the DDS channel.
The runtime does this for all channels upon core device startup."""
syscall("dds_init", now_mu(), self.channel)
dds_init(now_mu(), self.channel)
@kernel
def set_phase_mode(self, phase_mode):
@ -128,7 +146,7 @@ class _DDSGeneric:
"""
if phase_mode == _PHASE_MODE_DEFAULT:
phase_mode = self.phase_mode
syscall("dds_set", now_mu(), self.channel,
dds_set(now_mu(), self.channel,
frequency, round(phase*2**self.pow_width), phase_mode)
@kernel

View File

@ -1,134 +0,0 @@
import os
import llvmlite_or1k.ir as ll
import llvmlite_or1k.binding as llvm
from artiq.language import units
_syscalls = {
"now_init": "n:I",
"now_save": "I:n",
"watchdog_set": "i:i",
"watchdog_clear": "i:n",
"rtio_get_counter": "n:I",
"ttl_set_o": "Iib:n",
"ttl_set_oe": "Iib:n",
"ttl_set_sensitivity": "Iii:n",
"ttl_get": "iI:I",
"ttl_clock_set": "Iii:n",
"dds_init": "Ii:n",
"dds_batch_enter": "I:n",
"dds_batch_exit": "n:n",
"dds_set": "Iiiii:n",
}
def _chr_to_type(c):
if c == "n":
return ll.VoidType()
if c == "b":
return ll.IntType(1)
if c == "i":
return ll.IntType(32)
if c == "I":
return ll.IntType(64)
raise ValueError
def _str_to_functype(s):
assert(s[-2] == ":")
type_ret = _chr_to_type(s[-1])
type_args = [_chr_to_type(c) for c in s[:-2] if c != "n"]
return ll.FunctionType(type_ret, type_args)
def _chr_to_value(c):
if c == "n":
return base_types.VNone()
if c == "b":
return base_types.VBool()
if c == "i":
return base_types.VInt()
if c == "I":
return base_types.VInt(64)
raise ValueError
def _value_to_str(v):
if isinstance(v, base_types.VNone):
return "n"
if isinstance(v, base_types.VBool):
return "b"
if isinstance(v, base_types.VInt):
if v.nbits == 32:
return "i"
if v.nbits == 64:
return "I"
raise ValueError
if isinstance(v, base_types.VFloat):
return "f"
if isinstance(v, fractions.VFraction):
return "F"
if isinstance(v, lists.VList):
return "l" + _value_to_str(v.el_type)
raise ValueError
class LinkInterface:
def init_module(self, module):
self.module = module
llvm_module = self.module.llvm_module
# RPC
func_type = ll.FunctionType(ll.IntType(32), [ll.IntType(32)],
var_arg=1)
self.rpc = ll.Function(llvm_module, func_type, "__syscall_rpc")
# syscalls
self.syscalls = dict()
for func_name, func_type_str in _syscalls.items():
func_type = _str_to_functype(func_type_str)
self.syscalls[func_name] = ll.Function(
llvm_module, func_type, "__syscall_" + func_name)
def _build_rpc(self, args, builder):
r = base_types.VInt()
if builder is not None:
new_args = []
new_args.append(args[0].auto_load(builder)) # RPC number
for arg in args[1:]:
# type tag
arg_type_str = _value_to_str(arg)
arg_type_int = 0
for c in reversed(arg_type_str):
arg_type_int <<= 8
arg_type_int |= ord(c)
new_args.append(ll.Constant(ll.IntType(32), arg_type_int))
# pointer to value
if not isinstance(arg, base_types.VNone):
if isinstance(arg.llvm_value.type, ll.PointerType):
new_args.append(arg.llvm_value)
else:
arg_ptr = arg.new()
arg_ptr.alloca(builder)
arg_ptr.auto_store(builder, arg.llvm_value)
new_args.append(arg_ptr.llvm_value)
# end marker
new_args.append(ll.Constant(ll.IntType(32), 0))
r.auto_store(builder, builder.call(self.rpc, new_args))
return r
def _build_regular_syscall(self, syscall_name, args, builder):
r = _chr_to_value(_syscalls[syscall_name][-1])
if builder is not None:
args = [arg.auto_load(builder) for arg in args]
r.auto_store(builder, builder.call(self.syscalls[syscall_name],
args))
return r
def build_syscall(self, syscall_name, args, builder):
if syscall_name == "rpc":
return self._build_rpc(args, builder)
else:
return self._build_regular_syscall(syscall_name, args, builder)

View File

@ -1,4 +1,26 @@
from artiq.language.core import *
from artiq.language.types import *
@syscall
def ttl_set_o(time_mu: TInt64, channel: TInt32, enabled: TBool) -> TNone:
raise NotImplementedError("syscall not simulated")
@syscall
def ttl_set_oe(time_mu: TInt64, channel: TInt32, enabled: TBool) -> TNone:
raise NotImplementedError("syscall not simulated")
@syscall
def ttl_set_sensitivity(time_mu: TInt64, channel: TInt32, sensitivity: TInt32) -> TNone:
raise NotImplementedError("syscall not simulated")
@syscall
def ttl_get(channel: TInt32, time_limit_mu: TInt64) -> TInt64:
raise NotImplementedError("syscall not simulated")
@syscall
def ttl_clock_set(time_mu: TInt64, channel: TInt32, ftw: TInt32) -> TNone:
raise NotImplementedError("syscall not simulated")
class TTLOut:
@ -18,14 +40,14 @@ class TTLOut:
@kernel
def set_o(self, o):
syscall("ttl_set_o", now_mu(), self.channel, o)
ttl_set_o(now_mu(), self.channel, o)
self.o_previous_timestamp = now_mu()
@kernel
def sync(self):
"""Busy-wait until all programmed level switches have been
effected."""
while syscall("rtio_get_counter") < self.o_previous_timestamp:
while self.core.get_rtio_counter_mu() < self.o_previous_timestamp:
pass
@kernel
@ -83,7 +105,7 @@ class TTLInOut:
@kernel
def set_oe(self, oe):
syscall("ttl_set_oe", now_mu(), self.channel, oe)
ttl_set_oe(now_mu(), self.channel, oe)
@kernel
def output(self):
@ -95,14 +117,14 @@ class TTLInOut:
@kernel
def set_o(self, o):
syscall("ttl_set_o", now_mu(), self.channel, o)
ttl_set_o(now_mu(), self.channel, o)
self.o_previous_timestamp = now_mu()
@kernel
def sync(self):
"""Busy-wait until all programmed level switches have been
effected."""
while syscall("rtio_get_counter") < self.o_previous_timestamp:
while self.core.get_rtio_counter_mu() < self.o_previous_timestamp:
pass
@kernel
@ -133,7 +155,7 @@ class TTLInOut:
@kernel
def _set_sensitivity(self, value):
syscall("ttl_set_sensitivity", now_mu(), self.channel, value)
ttl_set_sensitivity(now_mu(), self.channel, value)
self.i_previous_timestamp = now_mu()
@kernel
@ -189,8 +211,7 @@ class TTLInOut:
"""Poll the RTIO input during all the previously programmed gate
openings, and returns the number of registered events."""
count = 0
while syscall("ttl_get", self.channel,
self.i_previous_timestamp) >= 0:
while ttl_get(self.channel, self.i_previous_timestamp) >= 0:
count += 1
return count
@ -201,7 +222,7 @@ class TTLInOut:
If the gate is permanently closed, returns a negative value.
"""
return syscall("ttl_get", self.channel, self.i_previous_timestamp)
return ttl_get(self.channel, self.i_previous_timestamp)
class TTLClockGen:
@ -254,7 +275,7 @@ class TTLClockGen:
that are not powers of two cause jitter of one RTIO clock cycle at the
output.
"""
syscall("ttl_clock_set", now_mu(), self.channel, frequency)
ttl_clock_set(now_mu(), self.channel, frequency)
self.previous_timestamp = now_mu()
@kernel
@ -271,5 +292,5 @@ class TTLClockGen:
def sync(self):
"""Busy-wait until all programmed frequency switches and stops have
been effected."""
while syscall("rtio_get_counter") < self.o_previous_timestamp:
while self.core.get_rtio_counter_mu() < self.o_previous_timestamp:
pass

View File

@ -7,15 +7,19 @@ from collections import namedtuple
from functools import wraps
__all__ = ["int64", "round64", "kernel", "portable",
"set_time_manager", "set_syscall_manager", "set_watchdog_factory",
__all__ = ["int64", "round64",
"kernel", "portable", "syscall",
"set_time_manager", "set_watchdog_factory",
"ARTIQException"]
# global namespace for kernels
kernel_globals = ("sequential", "parallel",
kernel_globals = (
"sequential", "parallel",
"delay_mu", "now_mu", "at_mu", "delay",
"seconds_to_mu", "mu_to_seconds",
"syscall", "watchdog")
"watchdog"
)
__all__.extend(kernel_globals)
@ -78,11 +82,12 @@ def round64(x):
return int64(round(x))
_ARTIQEmbeddedInfo = namedtuple("_ARTIQEmbeddedInfo", "core_name function")
_ARTIQEmbeddedInfo = namedtuple("_ARTIQEmbeddedInfo",
"core_name function syscall")
def kernel(arg):
"""This decorator marks an object's method for execution on the core
"""
This decorator marks an object's method for execution on the core
device.
When a decorated method is called from the Python interpreter, the ``core``
@ -106,15 +111,15 @@ def kernel(arg):
def run_on_core(self, *k_args, **k_kwargs):
return getattr(self, arg).run(function, ((self,) + k_args), k_kwargs)
run_on_core.artiq_embedded = _ARTIQEmbeddedInfo(
core_name=arg, function=function)
core_name=arg, function=function, syscall=None)
return run_on_core
return inner_decorator
else:
return kernel("core")(arg)
def portable(function):
"""This decorator marks a function for execution on the same device as its
"""
This decorator marks a function for execution on the same device as its
caller.
In other words, a decorated function called from the interpreter on the
@ -122,9 +127,31 @@ def portable(function):
core device). A decorated function called from a kernel will be executed
on the core device (no RPC).
"""
function.artiq_embedded = _ARTIQEmbeddedInfo(core_name="", function=function)
function.artiq_embedded = \
_ARTIQEmbeddedInfo(core_name=None, function=function, syscall=None)
return function
def syscall(arg):
"""
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
the Python function, if not provided) will be called. When executed on
host, the Python function will be called as usual.
Every argument and the return value must be annotated with ARTIQ types.
Only drivers should normally define syscalls.
"""
if isinstance(arg, str):
def inner_decorator(function):
function.artiq_embedded = \
_ARTIQEmbeddedInfo(core_name=None, function=None,
syscall=function.__name__)
return function
return inner_decorator
else:
return syscall(arg.__name__)(arg)
class _DummyTimeManager:
def _not_implemented(self, *args, **kwargs):
@ -152,22 +179,6 @@ def set_time_manager(time_manager):
_time_manager = time_manager
class _DummySyscallManager:
def do(self, *args):
raise NotImplementedError(
"Attempted to interpret kernel without a syscall manager")
_syscall_manager = _DummySyscallManager()
def set_syscall_manager(syscall_manager):
"""Set the system call manager used for simulating the core device's
runtime in the Python interpreter.
"""
global _syscall_manager
_syscall_manager = syscall_manager
class _Sequential:
"""In a sequential block, statements are executed one after another, with
the time increasing as one moves down the statement list."""
@ -240,17 +251,6 @@ def mu_to_seconds(mu, core=None):
return mu*core.ref_period
def syscall(*args):
"""Invokes a service of the runtime.
Kernels use this function to interface to the outside world: program RTIO
events, make RPCs, etc.
Only drivers should normally use ``syscall``.
"""
return _syscall_manager.do(*args)
class _DummyWatchdog:
def __init__(self, timeout):
pass