Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: m-labs/artiq
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: 8a28039b7477
Choose a base ref
...
head repository: m-labs/artiq
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: 0a9b2aecbc6e
Choose a head ref
  • 5 commits
  • 16 files changed
  • 2 contributors

Commits on Apr 26, 2016

  1. compiler: don't typecheck RPCs except for return type.

    Fixes #260.
    whitequark authored and sbourdeauducq committed Apr 26, 2016
    Copy the full SHA
    8d9a22f View commit details
  2. compiler: allow RPCing builtin functions.

    Fixes #366.
    whitequark authored and sbourdeauducq committed Apr 26, 2016
    Copy the full SHA
    0f6f684 View commit details
  3. doc: Document fast-math flag and kernel invariants.

    Fixes #351, #359.
    whitequark authored and sbourdeauducq committed Apr 26, 2016
    Copy the full SHA
    24e24dd View commit details
  4. embedding: ignore empty lines, like annotations, before kernel functi…

    …ons.
    
    Fixes #363.
    whitequark authored and sbourdeauducq committed Apr 26, 2016
    Copy the full SHA
    9b04778 View commit details
  5. Copy the full SHA
    0a9b2ae View commit details
2 changes: 1 addition & 1 deletion artiq/compiler/builtins.py
Original file line number Diff line number Diff line change
@@ -255,6 +255,6 @@ def is_allocated(typ):
return not (is_none(typ) or is_bool(typ) or is_int(typ) or
is_float(typ) or is_range(typ) or
types._is_pointer(typ) or types.is_function(typ) or
types.is_c_function(typ) or types.is_rpc_function(typ) or
types.is_c_function(typ) or types.is_rpc(typ) or
types.is_method(typ) or types.is_tuple(typ) or
types.is_value(typ))
84 changes: 40 additions & 44 deletions artiq/compiler/embedding.py
Original file line number Diff line number Diff line change
@@ -5,7 +5,7 @@
annotated as ``@kernel`` when they are referenced.
"""

import sys, os, re, linecache, inspect, textwrap
import sys, os, re, linecache, inspect, textwrap, types as pytypes
from collections import OrderedDict, defaultdict

from pythonparser import ast, algorithm, source, diagnostic, parse_buffer
@@ -102,7 +102,8 @@ def quote(self, value):
return asttyped.ListT(elts=elts, ctx=None, type=builtins.TList(),
begin_loc=begin_loc, end_loc=end_loc,
loc=begin_loc.join(end_loc))
elif inspect.isfunction(value) or inspect.ismethod(value):
elif inspect.isfunction(value) or inspect.ismethod(value) or \
isinstance(value, pytypes.BuiltinFunctionType):
quote_loc = self._add('`')
repr_loc = self._add(repr(value))
unquote_loc = self._add('`')
@@ -428,9 +429,8 @@ def _unify_attribute(self, result_type, value_node, attr_name, attr_loc, loc):
if attr_name not in attributes:
# We just figured out what the type should be. Add it.
attributes[attr_name] = attr_value_type
elif not types.is_rpc_function(attr_value_type):
else:
# Does this conflict with an earlier guess?
# RPC function types are exempt because RPCs are dynamically typed.
try:
attributes[attr_name].unify(attr_value_type)
except types.UnificationError as e:
@@ -611,10 +611,10 @@ def _function_loc(self, function):
line = function.__code__.co_firstlineno
name = function.__code__.co_name

source_line = linecache.getline(filename, line)
while source_line.lstrip().startswith("@"):
source_line = linecache.getline(filename, line).lstrip()
while source_line.startswith("@") or source_line == "":
line += 1
source_line = linecache.getline(filename, line)
source_line = linecache.getline(filename, line).lstrip()

if "<lambda>" in function.__qualname__:
column = 0 # can't get column of lambda
@@ -694,29 +694,22 @@ def proxy_diagnostic(diag):
# Let the rest of the program decide.
return types.TVar()

def _quote_foreign_function(self, function, loc, syscall, flags):
def _quote_syscall(self, function, loc):
signature = inspect.signature(function)

arg_types = OrderedDict()
optarg_types = OrderedDict()
for param in signature.parameters.values():
if param.kind not in (inspect.Parameter.POSITIONAL_ONLY,
inspect.Parameter.POSITIONAL_OR_KEYWORD):
# We pretend we don't see *args, kwpostargs=..., **kwargs.
# Since every method can be still invoked without any arguments
# going into *args and the slots after it, this is always safe,
# if sometimes constraining.
#
# Accepting POSITIONAL_ONLY is OK, because the compiler
# desugars the keyword arguments into positional ones internally.
continue
if param.kind != inspect.Parameter.POSITIONAL_OR_KEYWORD:
diag = diagnostic.Diagnostic("error",
"system calls must only use positional arguments; '{argument}' isn't",
{"argument": param.name},
self._function_loc(function),
notes=self._call_site_note(loc, is_syscall=True))
self.engine.process(diag)

if param.default is inspect.Parameter.empty:
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=False)
arg_types[param.name] = self._type_of_param(function, loc, param, is_syscall=True)
else:
diag = diagnostic.Diagnostic("error",
"system call argument '{argument}' must not have a default value",
@@ -727,26 +720,36 @@ def _quote_foreign_function(self, function, loc, syscall, flags):

if signature.return_annotation is not inspect.Signature.empty:
ret_type = self._extract_annot(function, signature.return_annotation,
"return type", loc, is_syscall=syscall is not None)
elif syscall is None:
ret_type = builtins.TNone()
else: # syscall is not None
"return type", loc, is_syscall=True)
else:
diag = diagnostic.Diagnostic("error",
"system call must have a return type annotation", {},
self._function_loc(function),
notes=self._call_site_note(loc, is_syscall=True))
self.engine.process(diag)
ret_type = types.TVar()

if syscall is None:
function_type = types.TRPCFunction(arg_types, optarg_types, ret_type,
service=self.object_map.store(function))
else:
function_type = types.TCFunction(arg_types, ret_type,
name=syscall, flags=flags)

function_type = types.TCFunction(arg_types, ret_type,
name=function.artiq_embedded.syscall,
flags=function.artiq_embedded.flags)
self.functions[function] = function_type
return function_type

def _quote_rpc(self, callee, loc):
ret_type = builtins.TNone()

if isinstance(callee, pytypes.BuiltinFunctionType):
pass
elif isinstance(callee, pytypes.FunctionType):
signature = inspect.signature(callee)
if signature.return_annotation is not inspect.Signature.empty:
ret_type = self._extract_annot(callee, signature.return_annotation,
"return type", loc, is_syscall=False)
else:
assert False

function_type = types.TRPC(ret_type, service=self.object_map.store(callee))
self.functions[callee] = function_type
return function_type

def _quote_function(self, function, loc):
@@ -780,9 +783,7 @@ def _quote_function(self, function, loc):
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.
self._quote_foreign_function(function, loc,
syscall=function.artiq_embedded.syscall,
flags=function.artiq_embedded.flags)
self._quote_syscall(function, loc)
elif function.artiq_embedded.forbidden is not None:
diag = diagnostic.Diagnostic("fatal",
"this function cannot be called as an RPC", {},
@@ -792,14 +793,9 @@ def _quote_function(self, function, loc):
else:
assert False
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, flags=None)
self._quote_rpc(function, loc)

function_type = self.functions[function]
if types.is_rpc_function(function_type):
function_type = types.instantiate(function_type)
return function_type
return self.functions[function]

def _quote(self, value, loc):
synthesizer = self._synthesizer(loc)
13 changes: 10 additions & 3 deletions artiq/compiler/ir.py
Original file line number Diff line number Diff line change
@@ -23,12 +23,19 @@ def is_basic_block(typ):
return isinstance(typ, TBasicBlock)

class TOption(types.TMono):
def __init__(self, inner):
super().__init__("option", {"inner": inner})
def __init__(self, value):
super().__init__("option", {"value": value})

def is_option(typ):
return isinstance(typ, TOption)

class TKeyword(types.TMono):
def __init__(self, value):
super().__init__("keyword", {"value": value})

def is_keyword(typ):
return isinstance(typ, TKeyword)

class TExceptionTypeInfo(types.TMono):
def __init__(self):
super().__init__("exntypeinfo")
@@ -678,7 +685,7 @@ def __init__(self, obj, attr, name=""):
typ = obj.type.attributes[attr]
else:
typ = obj.type.constructor.attributes[attr]
if types.is_function(typ):
if types.is_function(typ) or types.is_rpc(typ):
typ = types.TMethod(obj.type, typ)
super().__init__([obj], typ, name)
self.attr = attr
82 changes: 45 additions & 37 deletions artiq/compiler/transforms/artiq_ir_generator.py
Original file line number Diff line number Diff line change
@@ -922,9 +922,6 @@ def visit_AttributeT(self, node):
if self.current_assign is None:
return self.append(ir.GetAttr(obj, node.attr,
name="{}.FLD.{}".format(_readable_name(obj), node.attr)))
elif types.is_rpc_function(self.current_assign.type):
# RPC functions are just type-level markers
return self.append(ir.Builtin("nop", [], builtins.TNone()))
else:
return self.append(ir.SetAttr(obj, node.attr, self.current_assign))

@@ -1719,7 +1716,7 @@ def body_gen(index):
self.engine.process(diag)

def _user_call(self, callee, positional, keywords, arg_exprs={}):
if types.is_function(callee.type):
if types.is_function(callee.type) or types.is_rpc(callee.type):
func = callee
self_arg = None
fn_typ = callee.type
@@ -1734,40 +1731,51 @@ def _user_call(self, callee, positional, keywords, arg_exprs={}):
else:
assert False

args = [None] * (len(fn_typ.args) + len(fn_typ.optargs))

for index, arg in enumerate(positional):
if index + offset < len(fn_typ.args):
args[index + offset] = arg
if types.is_rpc(fn_typ):
if self_arg is None:
args = positional
else:
args[index + offset] = self.append(ir.Alloc([arg], ir.TOption(arg.type)))

for keyword in keywords:
arg = keywords[keyword]
if keyword in fn_typ.args:
for index, arg_name in enumerate(fn_typ.args):
if keyword == arg_name:
assert args[index] is None
args[index] = arg
break
elif keyword in fn_typ.optargs:
for index, optarg_name in enumerate(fn_typ.optargs):
if keyword == optarg_name:
assert args[len(fn_typ.args) + index] is None
args[len(fn_typ.args) + index] = \
self.append(ir.Alloc([arg], ir.TOption(arg.type)))
break

for index, optarg_name in enumerate(fn_typ.optargs):
if args[len(fn_typ.args) + index] is None:
args[len(fn_typ.args) + index] = \
self.append(ir.Alloc([], ir.TOption(fn_typ.optargs[optarg_name])))

if self_arg is not None:
assert args[0] is None
args[0] = self_arg

assert None not in args
args = [self_arg] + positional

for keyword in keywords:
arg = keywords[keyword]
args.append(self.append(ir.Alloc([ir.Constant(keyword, builtins.TStr()), arg],
ir.TKeyword(arg.type))))
else:
args = [None] * (len(fn_typ.args) + len(fn_typ.optargs))

for index, arg in enumerate(positional):
if index + offset < len(fn_typ.args):
args[index + offset] = arg
else:
args[index + offset] = self.append(ir.Alloc([arg], ir.TOption(arg.type)))

for keyword in keywords:
arg = keywords[keyword]
if keyword in fn_typ.args:
for index, arg_name in enumerate(fn_typ.args):
if keyword == arg_name:
assert args[index] is None
args[index] = arg
break
elif keyword in fn_typ.optargs:
for index, optarg_name in enumerate(fn_typ.optargs):
if keyword == optarg_name:
assert args[len(fn_typ.args) + index] is None
args[len(fn_typ.args) + index] = \
self.append(ir.Alloc([arg], ir.TOption(arg.type)))
break

for index, optarg_name in enumerate(fn_typ.optargs):
if args[len(fn_typ.args) + index] is None:
args[len(fn_typ.args) + index] = \
self.append(ir.Alloc([], ir.TOption(fn_typ.optargs[optarg_name])))

if self_arg is not None:
assert args[0] is None
args[0] = self_arg

assert None not in args

if self.unwind_target is None:
insn = self.append(ir.Call(func, args, arg_exprs))
18 changes: 12 additions & 6 deletions artiq/compiler/transforms/inferencer.py
Original file line number Diff line number Diff line change
@@ -109,17 +109,11 @@ def makenotes(printer, typea, typeb, loca, locb):
]

attr_type = object_type.attributes[attr_name]
if types.is_rpc_function(attr_type):
attr_type = types.instantiate(attr_type)

self._unify(result_type, attr_type, loc, None,
makenotes=makenotes, when=" for attribute '{}'".format(attr_name))
elif types.is_instance(object_type) and \
attr_name in object_type.constructor.attributes:
attr_type = object_type.constructor.attributes[attr_name].find()
if types.is_rpc_function(attr_type):
attr_type = types.instantiate(attr_type)

if types.is_function(attr_type):
# Convert to a method.
if len(attr_type.args) < 1:
@@ -155,6 +149,10 @@ def makenotes(printer, typea, typeb, loca, locb):
when=" while inferring the type for self argument")

attr_type = types.TMethod(object_type, attr_type)
elif types.is_rpc(attr_type):
# Convert to a method. We don't have to bother typechecking
# the self argument, since for RPCs anything goes.
attr_type = types.TMethod(object_type, attr_type)

if not types.is_var(attr_type):
self._unify(result_type, attr_type,
@@ -871,6 +869,10 @@ def visit_CallT(self, node):
return # not enough info yet
elif types.is_builtin(typ):
return self.visit_builtin_call(node)
elif types.is_rpc(typ):
self._unify(node.type, typ.ret,
node.loc, None)
return
elif not (types.is_function(typ) or types.is_method(typ)):
diag = diagnostic.Diagnostic("error",
"cannot call this expression of type {type}",
@@ -888,6 +890,10 @@ def visit_CallT(self, node):
typ = types.get_method_function(typ)
if types.is_var(typ):
return # not enough info yet
elif types.is_rpc(typ):
self._unify(node.type, typ.ret,
node.loc, None)
return

typ_arity = typ.arity() - 1
typ_args = OrderedDict(list(typ.args.items())[1:])
Loading