Skip to content

Commit cb22526

Browse files
author
whitequark
committedAug 27, 2015
Allow accessing attributes of embedded host objects.
1 parent 422208a commit cb22526

File tree

4 files changed

+151
-40
lines changed

4 files changed

+151
-40
lines changed
 

Diff for: ‎artiq/compiler/embedding.py

+106-15
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,13 @@
66
"""
77

88
import os, re, linecache, inspect
9-
from collections import OrderedDict
9+
from collections import OrderedDict, defaultdict
1010

1111
from pythonparser import ast, source, diagnostic, parse_buffer
1212

1313
from . import types, builtins, asttyped, prelude
1414
from .transforms import ASTTypedRewriter, Inferencer, IntMonomorphizer
15+
from .validators import MonomorphismValidator
1516

1617

1718
class ObjectMap:
@@ -34,10 +35,11 @@ def retrieve(self, obj_key):
3435
return self.forward_map[obj_key]
3536

3637
class ASTSynthesizer:
37-
def __init__(self, type_map, expanded_from=None):
38+
def __init__(self, type_map, value_map, expanded_from=None):
3839
self.source = ""
3940
self.source_buffer = source.Buffer(self.source, "<synthesized>")
40-
self.type_map, self.expanded_from = type_map, expanded_from
41+
self.type_map, self.value_map = type_map, value_map
42+
self.expanded_from = expanded_from
4143

4244
def finalize(self):
4345
self.source_buffer.source = self.source
@@ -82,6 +84,11 @@ def quote(self, value):
8284
begin_loc=begin_loc, end_loc=end_loc,
8385
loc=begin_loc.join(end_loc))
8486
else:
87+
quote_loc = self._add('`')
88+
repr_loc = self._add(repr(value))
89+
unquote_loc = self._add('`')
90+
loc = quote_loc.join(unquote_loc)
91+
8592
if isinstance(value, type):
8693
typ = value
8794
else:
@@ -98,16 +105,14 @@ def quote(self, value):
98105

99106
self.type_map[typ] = instance_type, constructor_type
100107

101-
quote_loc = self._add('`')
102-
repr_loc = self._add(repr(value))
103-
unquote_loc = self._add('`')
104-
105108
if isinstance(value, type):
109+
self.value_map[constructor_type].append((value, loc))
106110
return asttyped.QuoteT(value=value, type=constructor_type,
107-
loc=quote_loc.join(unquote_loc))
111+
loc=loc)
108112
else:
113+
self.value_map[instance_type].append((value, loc))
109114
return asttyped.QuoteT(value=value, type=instance_type,
110-
loc=quote_loc.join(unquote_loc))
115+
loc=loc)
111116

112117
def call(self, function_node, args, kwargs):
113118
"""
@@ -151,14 +156,16 @@ def call(self, function_node, args, kwargs):
151156
loc=name_loc.join(end_loc))
152157

153158
class StitchingASTTypedRewriter(ASTTypedRewriter):
154-
def __init__(self, engine, prelude, globals, host_environment, quote_function, type_map):
159+
def __init__(self, engine, prelude, globals, host_environment, quote_function,
160+
type_map, value_map):
155161
super().__init__(engine, prelude)
156162
self.globals = globals
157163
self.env_stack.append(self.globals)
158164

159165
self.host_environment = host_environment
160166
self.quote_function = quote_function
161167
self.type_map = type_map
168+
self.value_map = value_map
162169

163170
def visit_Name(self, node):
164171
typ = super()._try_find_name(node.id)
@@ -180,7 +187,9 @@ def visit_Name(self, node):
180187

181188
else:
182189
# It's just a value. Quote it.
183-
synthesizer = ASTSynthesizer(expanded_from=node.loc, type_map=self.type_map)
190+
synthesizer = ASTSynthesizer(expanded_from=node.loc,
191+
type_map=self.type_map,
192+
value_map=self.value_map)
184193
node = synthesizer.quote(value)
185194
synthesizer.finalize()
186195
return node
@@ -190,6 +199,83 @@ def visit_Name(self, node):
190199
node.loc)
191200
self.engine.process(diag)
192201

202+
class StitchingInferencer(Inferencer):
203+
def __init__(self, engine, type_map, value_map):
204+
super().__init__(engine)
205+
self.type_map, self.value_map = type_map, value_map
206+
207+
def visit_AttributeT(self, node):
208+
self.generic_visit(node)
209+
object_type = node.value.type.find()
210+
211+
# The inferencer can only observe types, not values; however,
212+
# when we work with host objects, we have to get the values
213+
# somewhere, since host interpreter does not have types.
214+
# Since we have categorized every host object we quoted according to
215+
# its type, we now interrogate every host object we have to ensure
216+
# that we can successfully serialize the value of the attribute we
217+
# are now adding at the code generation stage.
218+
#
219+
# FIXME: We perform exhaustive checks of every known host object every
220+
# time an attribute access is visited, which is potentially quadratic.
221+
# This is done because it is simpler than performing the checks only when:
222+
# * a previously unknown attribute is encountered,
223+
# * a previously unknown host object is encountered;
224+
# which would be the optimal solution.
225+
for object_value, object_loc in self.value_map[object_type]:
226+
if not hasattr(object_value, node.attr):
227+
note = diagnostic.Diagnostic("note",
228+
"attribute accessed here", {},
229+
node.loc)
230+
diag = diagnostic.Diagnostic("error",
231+
"host object does not have an attribute '{attr}'",
232+
{"attr": node.attr},
233+
object_loc, notes=[note])
234+
self.engine.process(diag)
235+
return
236+
237+
# Figure out what ARTIQ type does the value of the attribute have.
238+
# We do this by quoting it, as if to serialize. This has some
239+
# overhead (i.e. synthesizing a source buffer), but has the advantage
240+
# of having the host-to-ARTIQ mapping code in only one place and
241+
# also immediately getting proper diagnostics on type errors.
242+
synthesizer = ASTSynthesizer(type_map=self.type_map,
243+
value_map=self.value_map)
244+
ast = synthesizer.quote(getattr(object_value, node.attr))
245+
synthesizer.finalize()
246+
247+
def proxy_diagnostic(diag):
248+
note = diagnostic.Diagnostic("note",
249+
"expanded from here while trying to infer a type for an"
250+
" attribute '{attr}' of a host object",
251+
{"attr": node.attr},
252+
node.loc)
253+
diag.notes.append(note)
254+
255+
self.engine.process(diag)
256+
257+
proxy_engine = diagnostic.Engine()
258+
proxy_engine.process = proxy_diagnostic
259+
Inferencer(engine=proxy_engine).visit(ast)
260+
IntMonomorphizer(engine=proxy_engine).visit(ast)
261+
MonomorphismValidator(engine=proxy_engine).visit(ast)
262+
263+
if node.attr not in object_type.attributes:
264+
# We just figured out what the type should be. Add it.
265+
object_type.attributes[node.attr] = ast.type
266+
elif object_type.attributes[node.attr] != ast.type:
267+
# Does this conflict with an earlier guess?
268+
printer = types.TypePrinter()
269+
diag = diagnostic.Diagnostic("error",
270+
"host object has an attribute of type {typea}, which is"
271+
" different from previously inferred type {typeb}",
272+
{"typea": printer.name(ast.type),
273+
"typeb": printer.name(object_type.attributes[node.attr])},
274+
object_loc)
275+
self.engine.process(diag)
276+
277+
super().visit_AttributeT(node)
278+
193279
class Stitcher:
194280
def __init__(self, engine=None):
195281
if engine is None:
@@ -206,9 +292,11 @@ def __init__(self, engine=None):
206292

207293
self.object_map = ObjectMap()
208294
self.type_map = {}
295+
self.value_map = defaultdict(lambda: [])
209296

210297
def finalize(self):
211-
inferencer = Inferencer(engine=self.engine)
298+
inferencer = StitchingInferencer(engine=self.engine,
299+
type_map=self.type_map, value_map=self.value_map)
212300

213301
# Iterate inference to fixed point.
214302
self.inference_finished = False
@@ -262,7 +350,8 @@ def _quote_embedded_function(self, function):
262350
asttyped_rewriter = StitchingASTTypedRewriter(
263351
engine=self.engine, prelude=self.prelude,
264352
globals=self.globals, host_environment=host_environment,
265-
quote_function=self._quote_function, type_map=self.type_map)
353+
quote_function=self._quote_function,
354+
type_map=self.type_map, value_map=self.value_map)
266355
return asttyped_rewriter.visit(function_node)
267356

268357
def _function_loc(self, function):
@@ -324,7 +413,8 @@ def _type_of_param(self, function, loc, param, is_syscall):
324413
# This is tricky, because the default value might not have
325414
# a well-defined type in APython.
326415
# In this case, we bail out, but mention why we do it.
327-
synthesizer = ASTSynthesizer(type_map=self.type_map)
416+
synthesizer = ASTSynthesizer(type_map=self.type_map,
417+
value_map=self.value_map)
328418
ast = synthesizer.quote(param.default)
329419
synthesizer.finalize()
330420

@@ -442,7 +532,8 @@ def stitch_call(self, function, args, kwargs):
442532

443533
# We synthesize source code for the initial call so that
444534
# diagnostics would have something meaningful to display to the user.
445-
synthesizer = ASTSynthesizer(type_map=self.type_map)
535+
synthesizer = ASTSynthesizer(type_map=self.type_map,
536+
value_map=self.value_map)
446537
call_node = synthesizer.call(function_node, args, kwargs)
447538
synthesizer.finalize()
448539
self.typedtree.append(call_node)

Diff for: ‎artiq/compiler/transforms/inferencer.py

+11-1
Original file line numberDiff line numberDiff line change
@@ -130,10 +130,20 @@ def makenotes(printer, typea, typeb, loca, locb):
130130
self._unify(node.type, attr_type,
131131
node.loc, None)
132132
else:
133+
if node.attr_loc.source_buffer == node.value.loc.source_buffer:
134+
highlights, notes = [node.value.loc], []
135+
else:
136+
# This happens when the object being accessed is embedded
137+
# from the host program.
138+
note = diagnostic.Diagnostic("note",
139+
"object being accessed", {},
140+
node.value.loc)
141+
highlights, notes = [], [note]
142+
133143
diag = diagnostic.Diagnostic("error",
134144
"type {type} does not have an attribute '{attr}'",
135145
{"type": types.TypePrinter().name(object_type), "attr": node.attr},
136-
node.attr_loc, [node.value.loc])
146+
node.attr_loc, highlights, notes)
137147
self.engine.process(diag)
138148

139149
def _unify_iterable(self, element, collection):

Diff for: ‎artiq/compiler/transforms/llvm_ir_generator.py

+31-24
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,27 @@ def llty_of_type(self, typ, bare=False, for_return=False):
278278
else:
279279
return llty.as_pointer()
280280

281+
def llstr_of_str(self, value, name=None,
282+
linkage="private", unnamed_addr=True):
283+
if isinstance(value, str):
284+
assert "\0" not in value
285+
as_bytes = (value + "\0").encode("utf-8")
286+
else:
287+
as_bytes = value
288+
289+
if name is None:
290+
name = self.llmodule.get_unique_name("str")
291+
292+
llstr = self.llmodule.get_global(name)
293+
if llstr is None:
294+
llstrty = ll.ArrayType(lli8, len(as_bytes))
295+
llstr = ll.GlobalVariable(self.llmodule, llstrty, name)
296+
llstr.global_constant = True
297+
llstr.initializer = ll.Constant(llstrty, bytearray(as_bytes))
298+
llstr.linkage = linkage
299+
llstr.unnamed_addr = unnamed_addr
300+
return llstr.bitcast(llptr)
301+
281302
def llconst_of_const(self, const):
282303
llty = self.llty_of_type(const.type)
283304
if const.value is None:
@@ -289,33 +310,19 @@ def llconst_of_const(self, const):
289310
elif isinstance(const.value, (int, float)):
290311
return ll.Constant(llty, const.value)
291312
elif isinstance(const.value, (str, bytes)):
292-
if isinstance(const.value, str):
293-
assert "\0" not in const.value
294-
as_bytes = (const.value + "\0").encode("utf-8")
295-
else:
296-
as_bytes = const.value
297-
298313
if ir.is_exn_typeinfo(const.type):
299314
# Exception typeinfo; should be merged with identical others
300315
name = "__artiq_exn_" + const.value
301316
linkage = "linkonce"
302317
unnamed_addr = False
303318
else:
304319
# Just a string
305-
name = self.llmodule.get_unique_name("str")
320+
name = None
306321
linkage = "private"
307322
unnamed_addr = True
308323

309-
llconst = self.llmodule.get_global(name)
310-
if llconst is None:
311-
llstrty = ll.ArrayType(lli8, len(as_bytes))
312-
llconst = ll.GlobalVariable(self.llmodule, llstrty, name)
313-
llconst.global_constant = True
314-
llconst.initializer = ll.Constant(llstrty, bytearray(as_bytes))
315-
llconst.linkage = linkage
316-
llconst.unnamed_addr = unnamed_addr
317-
318-
return llconst.bitcast(llptr)
324+
return self.llstr_of_str(const.value, name=name,
325+
linkage=linkage, unnamed_addr=unnamed_addr)
319326
else:
320327
assert False
321328

@@ -856,7 +863,7 @@ def ret_error_handler(typ):
856863
tag += self._rpc_tag(fun_type.ret, ret_error_handler)
857864
tag += b"\x00"
858865

859-
lltag = self.llconst_of_const(ir.Constant(tag + b"\x00", builtins.TStr()))
866+
lltag = self.llstr_of_str(tag)
860867

861868
llstackptr = self.llbuilder.call(self.llbuiltin("llvm.stacksave"), [])
862869

@@ -981,24 +988,24 @@ def _quote(self, value, typ, path):
981988
global_name = "object.{}".format(objectid)
982989
else:
983990
llfields.append(self._quote(getattr(value, attr), typ.attributes[attr],
984-
path + [attr]))
991+
lambda: path() + [attr]))
985992

986993
llvalue = ll.Constant.literal_struct(llfields)
987994
elif builtins.is_none(typ):
988995
assert value is None
989-
return self.llconst_of_const(value)
996+
return ll.Constant.literal_struct([])
990997
elif builtins.is_bool(typ):
991998
assert value in (True, False)
992-
return self.llconst_of_const(value)
999+
return ll.Constant(lli1, value)
9931000
elif builtins.is_int(typ):
9941001
assert isinstance(value, int)
995-
return self.llconst_of_const(value)
1002+
return ll.Constant(ll.IntType(builtins.get_int_width(typ)), value)
9961003
elif builtins.is_float(typ):
9971004
assert isinstance(value, float)
998-
return self.llconst_of_const(value)
1005+
return ll.Constant(lldouble, value)
9991006
elif builtins.is_str(typ):
10001007
assert isinstance(value, (str, bytes))
1001-
return self.llconst_of_const(value)
1008+
return self.llstr_of_str(value)
10021009
else:
10031010
assert False
10041011

Diff for: ‎artiq/compiler/types.py

+3
Original file line numberDiff line numberDiff line change
@@ -320,6 +320,9 @@ def __eq__(self, other):
320320
def __ne__(self, other):
321321
return not (self == other)
322322

323+
def __hash__(self):
324+
return hash(self.name)
325+
323326
class TBuiltinFunction(TBuiltin):
324327
"""
325328
A type of a builtin function.

0 commit comments

Comments
 (0)
Please sign in to comment.