Allow accessing attributes of embedded host objects.

This commit is contained in:
whitequark 2015-08-27 05:01:04 -05:00
parent 422208a0e9
commit cb225269ff
4 changed files with 151 additions and 40 deletions

View File

@ -6,12 +6,13 @@ annotated as ``@kernel`` when they are referenced.
"""
import os, re, linecache, inspect
from collections import OrderedDict
from collections import OrderedDict, defaultdict
from pythonparser import ast, source, diagnostic, parse_buffer
from . import types, builtins, asttyped, prelude
from .transforms import ASTTypedRewriter, Inferencer, IntMonomorphizer
from .validators import MonomorphismValidator
class ObjectMap:
@ -34,10 +35,11 @@ class ObjectMap:
return self.forward_map[obj_key]
class ASTSynthesizer:
def __init__(self, type_map, expanded_from=None):
def __init__(self, type_map, value_map, expanded_from=None):
self.source = ""
self.source_buffer = source.Buffer(self.source, "<synthesized>")
self.type_map, self.expanded_from = type_map, expanded_from
self.type_map, self.value_map = type_map, value_map
self.expanded_from = expanded_from
def finalize(self):
self.source_buffer.source = self.source
@ -82,6 +84,11 @@ class ASTSynthesizer:
begin_loc=begin_loc, end_loc=end_loc,
loc=begin_loc.join(end_loc))
else:
quote_loc = self._add('`')
repr_loc = self._add(repr(value))
unquote_loc = self._add('`')
loc = quote_loc.join(unquote_loc)
if isinstance(value, type):
typ = value
else:
@ -98,16 +105,14 @@ class ASTSynthesizer:
self.type_map[typ] = instance_type, constructor_type
quote_loc = self._add('`')
repr_loc = self._add(repr(value))
unquote_loc = self._add('`')
if isinstance(value, type):
self.value_map[constructor_type].append((value, loc))
return asttyped.QuoteT(value=value, type=constructor_type,
loc=quote_loc.join(unquote_loc))
loc=loc)
else:
self.value_map[instance_type].append((value, loc))
return asttyped.QuoteT(value=value, type=instance_type,
loc=quote_loc.join(unquote_loc))
loc=loc)
def call(self, function_node, args, kwargs):
"""
@ -151,7 +156,8 @@ class ASTSynthesizer:
loc=name_loc.join(end_loc))
class StitchingASTTypedRewriter(ASTTypedRewriter):
def __init__(self, engine, prelude, globals, host_environment, quote_function, type_map):
def __init__(self, engine, prelude, globals, host_environment, quote_function,
type_map, value_map):
super().__init__(engine, prelude)
self.globals = globals
self.env_stack.append(self.globals)
@ -159,6 +165,7 @@ class StitchingASTTypedRewriter(ASTTypedRewriter):
self.host_environment = host_environment
self.quote_function = quote_function
self.type_map = type_map
self.value_map = value_map
def visit_Name(self, node):
typ = super()._try_find_name(node.id)
@ -180,7 +187,9 @@ class StitchingASTTypedRewriter(ASTTypedRewriter):
else:
# It's just a value. Quote it.
synthesizer = ASTSynthesizer(expanded_from=node.loc, type_map=self.type_map)
synthesizer = ASTSynthesizer(expanded_from=node.loc,
type_map=self.type_map,
value_map=self.value_map)
node = synthesizer.quote(value)
synthesizer.finalize()
return node
@ -190,6 +199,83 @@ class StitchingASTTypedRewriter(ASTTypedRewriter):
node.loc)
self.engine.process(diag)
class StitchingInferencer(Inferencer):
def __init__(self, engine, type_map, value_map):
super().__init__(engine)
self.type_map, self.value_map = type_map, value_map
def visit_AttributeT(self, node):
self.generic_visit(node)
object_type = node.value.type.find()
# The inferencer can only observe types, not values; however,
# when we work with host objects, we have to get the values
# somewhere, since host interpreter does not have types.
# Since we have categorized every host object we quoted according to
# 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.
for object_value, object_loc in self.value_map[object_type]:
if not hasattr(object_value, node.attr):
note = diagnostic.Diagnostic("note",
"attribute accessed here", {},
node.loc)
diag = diagnostic.Diagnostic("error",
"host object does not have an attribute '{attr}'",
{"attr": node.attr},
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.
synthesizer = ASTSynthesizer(type_map=self.type_map,
value_map=self.value_map)
ast = synthesizer.quote(getattr(object_value, node.attr))
synthesizer.finalize()
def proxy_diagnostic(diag):
note = diagnostic.Diagnostic("note",
"expanded from here while trying to infer a type for an"
" attribute '{attr}' of a host object",
{"attr": node.attr},
node.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)
MonomorphismValidator(engine=proxy_engine).visit(ast)
if node.attr not in object_type.attributes:
# We just figured out what the type should be. Add it.
object_type.attributes[node.attr] = ast.type
elif object_type.attributes[node.attr] != ast.type:
# Does this conflict with an earlier guess?
printer = types.TypePrinter()
diag = diagnostic.Diagnostic("error",
"host object has an attribute of type {typea}, which is"
" different from previously inferred type {typeb}",
{"typea": printer.name(ast.type),
"typeb": printer.name(object_type.attributes[node.attr])},
object_loc)
self.engine.process(diag)
super().visit_AttributeT(node)
class Stitcher:
def __init__(self, engine=None):
if engine is None:
@ -206,9 +292,11 @@ class Stitcher:
self.object_map = ObjectMap()
self.type_map = {}
self.value_map = defaultdict(lambda: [])
def finalize(self):
inferencer = Inferencer(engine=self.engine)
inferencer = StitchingInferencer(engine=self.engine,
type_map=self.type_map, value_map=self.value_map)
# Iterate inference to fixed point.
self.inference_finished = False
@ -262,7 +350,8 @@ class Stitcher:
asttyped_rewriter = StitchingASTTypedRewriter(
engine=self.engine, prelude=self.prelude,
globals=self.globals, host_environment=host_environment,
quote_function=self._quote_function, type_map=self.type_map)
quote_function=self._quote_function,
type_map=self.type_map, value_map=self.value_map)
return asttyped_rewriter.visit(function_node)
def _function_loc(self, function):
@ -324,7 +413,8 @@ class Stitcher:
# This is tricky, because the default value might not have
# a well-defined type in APython.
# In this case, we bail out, but mention why we do it.
synthesizer = ASTSynthesizer(type_map=self.type_map)
synthesizer = ASTSynthesizer(type_map=self.type_map,
value_map=self.value_map)
ast = synthesizer.quote(param.default)
synthesizer.finalize()
@ -442,7 +532,8 @@ class Stitcher:
# We synthesize source code for the initial call so that
# diagnostics would have something meaningful to display to the user.
synthesizer = ASTSynthesizer(type_map=self.type_map)
synthesizer = ASTSynthesizer(type_map=self.type_map,
value_map=self.value_map)
call_node = synthesizer.call(function_node, args, kwargs)
synthesizer.finalize()
self.typedtree.append(call_node)

View File

@ -130,10 +130,20 @@ class Inferencer(algorithm.Visitor):
self._unify(node.type, attr_type,
node.loc, None)
else:
if node.attr_loc.source_buffer == node.value.loc.source_buffer:
highlights, notes = [node.value.loc], []
else:
# This happens when the object being accessed is embedded
# from the host program.
note = diagnostic.Diagnostic("note",
"object being accessed", {},
node.value.loc)
highlights, notes = [], [note]
diag = diagnostic.Diagnostic("error",
"type {type} does not have an attribute '{attr}'",
{"type": types.TypePrinter().name(object_type), "attr": node.attr},
node.attr_loc, [node.value.loc])
node.attr_loc, highlights, notes)
self.engine.process(diag)
def _unify_iterable(self, element, collection):

View File

@ -278,6 +278,27 @@ class LLVMIRGenerator:
else:
return llty.as_pointer()
def llstr_of_str(self, value, name=None,
linkage="private", unnamed_addr=True):
if isinstance(value, str):
assert "\0" not in value
as_bytes = (value + "\0").encode("utf-8")
else:
as_bytes = value
if name is None:
name = self.llmodule.get_unique_name("str")
llstr = self.llmodule.get_global(name)
if llstr is None:
llstrty = ll.ArrayType(lli8, len(as_bytes))
llstr = ll.GlobalVariable(self.llmodule, llstrty, name)
llstr.global_constant = True
llstr.initializer = ll.Constant(llstrty, bytearray(as_bytes))
llstr.linkage = linkage
llstr.unnamed_addr = unnamed_addr
return llstr.bitcast(llptr)
def llconst_of_const(self, const):
llty = self.llty_of_type(const.type)
if const.value is None:
@ -289,12 +310,6 @@ class LLVMIRGenerator:
elif isinstance(const.value, (int, float)):
return ll.Constant(llty, const.value)
elif isinstance(const.value, (str, bytes)):
if isinstance(const.value, str):
assert "\0" not in const.value
as_bytes = (const.value + "\0").encode("utf-8")
else:
as_bytes = const.value
if ir.is_exn_typeinfo(const.type):
# Exception typeinfo; should be merged with identical others
name = "__artiq_exn_" + const.value
@ -302,20 +317,12 @@ class LLVMIRGenerator:
unnamed_addr = False
else:
# Just a string
name = self.llmodule.get_unique_name("str")
name = None
linkage = "private"
unnamed_addr = True
llconst = self.llmodule.get_global(name)
if llconst is None:
llstrty = ll.ArrayType(lli8, len(as_bytes))
llconst = ll.GlobalVariable(self.llmodule, llstrty, name)
llconst.global_constant = True
llconst.initializer = ll.Constant(llstrty, bytearray(as_bytes))
llconst.linkage = linkage
llconst.unnamed_addr = unnamed_addr
return llconst.bitcast(llptr)
return self.llstr_of_str(const.value, name=name,
linkage=linkage, unnamed_addr=unnamed_addr)
else:
assert False
@ -856,7 +863,7 @@ class LLVMIRGenerator:
tag += self._rpc_tag(fun_type.ret, ret_error_handler)
tag += b"\x00"
lltag = self.llconst_of_const(ir.Constant(tag + b"\x00", builtins.TStr()))
lltag = self.llstr_of_str(tag)
llstackptr = self.llbuilder.call(self.llbuiltin("llvm.stacksave"), [])
@ -981,24 +988,24 @@ class LLVMIRGenerator:
global_name = "object.{}".format(objectid)
else:
llfields.append(self._quote(getattr(value, attr), typ.attributes[attr],
path + [attr]))
lambda: path() + [attr]))
llvalue = ll.Constant.literal_struct(llfields)
elif builtins.is_none(typ):
assert value is None
return self.llconst_of_const(value)
return ll.Constant.literal_struct([])
elif builtins.is_bool(typ):
assert value in (True, False)
return self.llconst_of_const(value)
return ll.Constant(lli1, value)
elif builtins.is_int(typ):
assert isinstance(value, int)
return self.llconst_of_const(value)
return ll.Constant(ll.IntType(builtins.get_int_width(typ)), value)
elif builtins.is_float(typ):
assert isinstance(value, float)
return self.llconst_of_const(value)
return ll.Constant(lldouble, value)
elif builtins.is_str(typ):
assert isinstance(value, (str, bytes))
return self.llconst_of_const(value)
return self.llstr_of_str(value)
else:
assert False

View File

@ -320,6 +320,9 @@ class TBuiltin(Type):
def __ne__(self, other):
return not (self == other)
def __hash__(self):
return hash(self.name)
class TBuiltinFunction(TBuiltin):
"""
A type of a builtin function.