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: 8e77e561cdd0
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: f838b8be497a
Choose a head ref
  • 3 commits
  • 2 files changed
  • 1 contributor

Commits on Feb 25, 2016

  1. compiler: quell excessively detailed diagnostics.

    whitequark committed Feb 25, 2016
    Copy the full SHA
    919a49b View commit details
  2. compiler.types: TDelay is always unifiable with self.

    whitequark committed Feb 25, 2016
    Copy the full SHA
    d899d73 View commit details
  3. compiler.embedding: cache attribute types (fixes #276).

    whitequark committed Feb 25, 2016
    Copy the full SHA
    f838b8b View commit details
Showing with 123 additions and 116 deletions.
  1. +103 −100 artiq/compiler/embedding.py
  2. +20 −16 artiq/compiler/types.py
203 changes: 103 additions & 100 deletions artiq/compiler/embedding.py
Original file line number Diff line number Diff line change
@@ -295,6 +295,102 @@ def __init__(self, engine, value_map, quote):
super().__init__(engine)
self.value_map = value_map
self.quote = quote
self.attr_type_cache = {}

def _compute_value_type(self, object_value, object_type, object_loc, attr_name, loc):
if not hasattr(object_value, attr_name):
if attr_name.startswith('_'):
names = set(filter(lambda name: not name.startswith('_'),
dir(object_value)))
else:
names = set(dir(object_value))
suggestion = suggest_identifier(attr_name, names)

note = diagnostic.Diagnostic("note",
"attribute accessed here", {},
loc)
if suggestion is not None:
diag = diagnostic.Diagnostic("error",
"host object does not have an attribute '{attr}'; "
"did you mean '{suggestion}'?",
{"attr": attr_name, "suggestion": suggestion},
object_loc, notes=[note])
else:
diag = diagnostic.Diagnostic("error",
"host object does not have an attribute '{attr}'",
{"attr": attr_name},
object_loc, notes=[note])
self.engine.process(diag)
return

# Figure out what ARTIQ type does the value of the attribute have.
# We do this by quoting it, as if to serialize. This has some
# overhead (i.e. synthesizing a source buffer), but has the advantage
# of having the host-to-ARTIQ mapping code in only one place and
# also immediately getting proper diagnostics on type errors.
attr_value = getattr(object_value, attr_name)
if inspect.ismethod(attr_value) and types.is_instance(object_type):
# In cases like:
# class c:
# @kernel
# def f(self): pass
# we want f to be defined on the class, not on the instance.
attributes = object_type.constructor.attributes
attr_value = attr_value.__func__
else:
attributes = object_type.attributes

attr_value_type = None

if isinstance(attr_value, list):
# Fast path for lists of scalars.
IS_FLOAT = 1
IS_INT32 = 2
IS_INT64 = 4

state = 0
for elt in attr_value:
if elt.__class__ == float:
state |= IS_FLOAT
elif elt.__class__ == int:
if -2**31 < elt < 2**31-1:
state |= IS_INT32
elif -2**63 < elt < 2**63-1:
state |= IS_INT64
else:
state = -1
break
else:
state = -1

if state == IS_FLOAT:
attr_value_type = builtins.TList(builtins.TFloat())
elif state == IS_INT32:
attr_value_type = builtins.TList(builtins.TInt32())
elif state == IS_INT64:
attr_value_type = builtins.TList(builtins.TInt64())

if attr_value_type is None:
# Slow path. We don't know what exactly is the attribute value,
# so we quote it only for the error message that may possibly result.
ast = self.quote(attr_value, object_loc.expanded_from)

def proxy_diagnostic(diag):
note = diagnostic.Diagnostic("note",
"while inferring a type for an attribute '{attr}' of a host object",
{"attr": attr_name},
loc)
diag.notes.append(note)

self.engine.process(diag)

proxy_engine = diagnostic.Engine()
proxy_engine.process = proxy_diagnostic
Inferencer(engine=proxy_engine).visit(ast)
IntMonomorphizer(engine=proxy_engine).visit(ast)
attr_value_type = ast.type

return attributes, attr_value_type

def _unify_attribute(self, result_type, value_node, attr_name, attr_loc, loc):
# The inferencer can only observe types, not values; however,
@@ -304,108 +400,15 @@ def _unify_attribute(self, result_type, value_node, attr_name, attr_loc, loc):
# its type, we now interrogate every host object we have to ensure
# that we can successfully serialize the value of the attribute we
# are now adding at the code generation stage.
#
# FIXME: We perform exhaustive checks of every known host object every
# time an attribute access is visited, which is potentially quadratic.
# This is done because it is simpler than performing the checks only when:
# * a previously unknown attribute is encountered,
# * a previously unknown host object is encountered;
# which would be the optimal solution.

object_type = value_node.type.find()
for object_value, object_loc in self.value_map[object_type]:
attr_value_type = None
if not hasattr(object_value, attr_name):
if attr_name.startswith('_'):
names = set(filter(lambda name: not name.startswith('_'),
dir(object_value)))
else:
names = set(dir(object_value))
suggestion = suggest_identifier(attr_name, names)

note = diagnostic.Diagnostic("note",
"attribute accessed here", {},
loc)
if suggestion is not None:
diag = diagnostic.Diagnostic("error",
"host object does not have an attribute '{attr}'; "
"did you mean '{suggestion}'?",
{"attr": attr_name, "suggestion": suggestion},
object_loc, notes=[note])
else:
diag = diagnostic.Diagnostic("error",
"host object does not have an attribute '{attr}'",
{"attr": attr_name},
object_loc, notes=[note])
self.engine.process(diag)
return

# Figure out what ARTIQ type does the value of the attribute have.
# We do this by quoting it, as if to serialize. This has some
# overhead (i.e. synthesizing a source buffer), but has the advantage
# of having the host-to-ARTIQ mapping code in only one place and
# also immediately getting proper diagnostics on type errors.
attr_value = getattr(object_value, attr_name)
if inspect.ismethod(attr_value) and types.is_instance(object_type):
# In cases like:
# class c:
# @kernel
# def f(self): pass
# we want f to be defined on the class, not on the instance.
attributes = object_type.constructor.attributes
attr_value = attr_value.__func__
is_method = True
else:
attributes = object_type.attributes
is_method = False

if isinstance(attr_value, list):
# Fast path for lists of scalars.
IS_FLOAT = 1
IS_INT32 = 2
IS_INT64 = 4

state = 0
for elt in attr_value:
if elt.__class__ == float:
state |= IS_FLOAT
elif elt.__class__ == int:
if -2**31 < elt < 2**31-1:
state |= IS_INT32
elif -2**63 < elt < 2**63-1:
state |= IS_INT64
else:
state = -1
break
else:
state = -1

if state == IS_FLOAT:
attr_value_type = builtins.TList(builtins.TFloat())
elif state == IS_INT32:
attr_value_type = builtins.TList(builtins.TInt32())
elif state == IS_INT64:
attr_value_type = builtins.TList(builtins.TInt64())

if attr_value_type is None:
# Slow path. We don't know what exactly is the attribute value,
# so we quote it only for the error message that may possibly result.
ast = self.quote(attr_value, object_loc.expanded_from)

def proxy_diagnostic(diag):
note = diagnostic.Diagnostic("note",
"while inferring a type for an attribute '{attr}' of a host object",
{"attr": attr_name},
loc)
diag.notes.append(note)

self.engine.process(diag)

proxy_engine = diagnostic.Engine()
proxy_engine.process = proxy_diagnostic
Inferencer(engine=proxy_engine).visit(ast)
IntMonomorphizer(engine=proxy_engine).visit(ast)
attr_value_type = ast.type
attr_type_key = (id(object_value), attr_name)
try:
attributes, attr_value_type = self.attr_type_cache[attr_type_key]
except KeyError:
attributes, attr_value_type = \
self._compute_value_type(object_value, object_type, object_loc, attr_name, loc)
self.attr_type_cache[attr_type_key] = attributes, attr_value_type

if attr_name not in attributes:
# We just figured out what the type should be. Add it.
36 changes: 20 additions & 16 deletions artiq/compiler/types.py
Original file line number Diff line number Diff line change
@@ -527,7 +527,7 @@ def unify(self, other):
elif self.is_fixed() and other.is_fixed() and \
self.duration.fold() == other.duration.fold():
pass
else:
elif self is not other:
raise UnificationError(self, other)

def fold(self, accum, fn):
@@ -694,42 +694,45 @@ def __init__(self):
self.map = {}
self.recurse_guard = set()

def name(self, typ):
def name(self, typ, depth=0, max_depth=1):
typ = typ.find()
if isinstance(typ, TVar):
if typ not in self.map:
self.map[typ] = "'%s" % next(self.gen)
return self.map[typ]
elif isinstance(typ, TInstance):
if typ in self.recurse_guard:
if typ in self.recurse_guard or depth >= max_depth:
return "<instance {}>".format(typ.name)
else:
self.recurse_guard.add(typ)
attrs = ", ".join(["{}: {}".format(attr, self.name(typ.attributes[attr]))
for attr in typ.attributes])
return "<instance {} {{{}}}>".format(typ.name, attrs)
attrs = ",\n\t\t".join(["{}: {}".format(attr, self.name(typ.attributes[attr],
depth + 1))
for attr in typ.attributes])
return "<instance {} {{\n\t\t{}\n\t}}>".format(typ.name, attrs)
elif isinstance(typ, TMono):
if typ.params == {}:
return typ.name
else:
return "%s(%s)" % (typ.name, ", ".join(
["%s=%s" % (k, self.name(typ.params[k])) for k in typ.params]))
["%s=%s" % (k, self.name(typ.params[k], depth + 1)) for k in typ.params]))
elif isinstance(typ, TTuple):
if len(typ.elts) == 1:
return "(%s,)" % self.name(typ.elts[0])
return "(%s,)" % self.name(typ.elts[0], depth + 1)
else:
return "(%s)" % ", ".join(list(map(self.name, typ.elts)))
return "(%s)" % ", ".join([self.name(typ, depth + 1) for typ in typ.elts])
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]
signature = "(%s)->%s" % (", ".join(args), self.name(typ.ret))
args += [ "%s:%s" % (arg, self.name(typ.args[arg], depth + 1))
for arg in typ.args]
args += ["?%s:%s" % (arg, self.name(typ.optargs[arg], depth + 1))
for arg in typ.optargs]
signature = "(%s)->%s" % (", ".join(args), self.name(typ.ret, depth + 1))

delay = typ.delay.find()
if isinstance(delay, TVar):
signature += " delay({})".format(self.name(delay))
signature += " delay({})".format(self.name(delay, depth + 1))
elif not (delay.is_fixed() and iodelay.is_zero(delay.duration)):
signature += " " + self.name(delay)
signature += " " + self.name(delay, depth + 1)

if isinstance(typ, TRPCFunction):
return "[rpc #{}]{}".format(typ.service, signature)
@@ -740,11 +743,12 @@ def name(self, typ):
elif isinstance(typ, TBuiltinFunction):
return "<function {}>".format(typ.name)
elif isinstance(typ, (TConstructor, TExceptionConstructor)):
if typ in self.recurse_guard:
if typ in self.recurse_guard or depth >= max_depth:
return "<constructor {}>".format(typ.name)
else:
self.recurse_guard.add(typ)
attrs = ", ".join(["{}: {}".format(attr, self.name(typ.attributes[attr]))
attrs = ", ".join(["{}: {}".format(attr, self.name(typ.attributes[attr],
depth + 1))
for attr in typ.attributes])
return "<constructor {} {{{}}}>".format(typ.name, attrs)
elif isinstance(typ, TBuiltin):