PageRenderTime 52ms CodeModel.GetById 19ms RepoModel.GetById 1ms app.codeStats 0ms

/rpython/annotator/annrpython.py

https://bitbucket.org/pypy/pypy/
Python | 638 lines | 625 code | 8 blank | 5 comment | 7 complexity | c19dee4eaed46f7b9fa33e81f8c65ee7 MD5 | raw file
Possible License(s): AGPL-3.0, BSD-3-Clause, Apache-2.0
  1. from __future__ import absolute_import
  2. import types
  3. from collections import defaultdict
  4. from rpython.tool.ansi_print import AnsiLogger
  5. from rpython.tool.pairtype import pair
  6. from rpython.tool.error import (format_blocked_annotation_error,
  7. gather_error, source_lines)
  8. from rpython.flowspace.model import Variable, Constant, checkgraph
  9. from rpython.translator import simplify, transform
  10. from rpython.annotator import model as annmodel, signature
  11. from rpython.annotator.model import (
  12. typeof, s_ImpossibleValue, SomeInstance, intersection, difference)
  13. from rpython.annotator.bookkeeper import Bookkeeper
  14. from rpython.rtyper.normalizecalls import perform_normalizations
  15. log = AnsiLogger("annrpython")
  16. class RPythonAnnotator(object):
  17. """Block annotator for RPython.
  18. See description in doc/translation.txt."""
  19. def __init__(self, translator=None, policy=None, bookkeeper=None):
  20. import rpython.rtyper.extfuncregistry # has side effects
  21. if translator is None:
  22. # interface for tests
  23. from rpython.translator.translator import TranslationContext
  24. translator = TranslationContext()
  25. translator.annotator = self
  26. self.translator = translator
  27. self.pendingblocks = {} # map {block: graph-containing-it}
  28. self.annotated = {} # set of blocks already seen
  29. self.added_blocks = None # see processblock() below
  30. self.links_followed = {} # set of links that have ever been followed
  31. self.notify = {} # {block: {positions-to-reflow-from-when-done}}
  32. self.fixed_graphs = {} # set of graphs not to annotate again
  33. self.blocked_blocks = {} # set of {blocked_block: (graph, index)}
  34. # --- the following information is recorded for debugging ---
  35. self.blocked_graphs = {} # set of graphs that have blocked blocks
  36. # --- end of debugging information ---
  37. self.frozen = False
  38. if policy is None:
  39. from rpython.annotator.policy import AnnotatorPolicy
  40. self.policy = AnnotatorPolicy()
  41. else:
  42. self.policy = policy
  43. if bookkeeper is None:
  44. bookkeeper = Bookkeeper(self)
  45. self.bookkeeper = bookkeeper
  46. def __getstate__(self):
  47. attrs = """translator pendingblocks annotated links_followed
  48. notify bookkeeper frozen policy added_blocks""".split()
  49. ret = self.__dict__.copy()
  50. for key, value in ret.items():
  51. if key not in attrs:
  52. assert type(value) is dict, (
  53. "%r is not dict. please update %s.__getstate__" %
  54. (key, self.__class__.__name__))
  55. ret[key] = {}
  56. return ret
  57. #___ convenience high-level interface __________________
  58. def build_types(self, function, input_arg_types, complete_now=True,
  59. main_entry_point=False):
  60. """Recursively build annotations about the specific entry point."""
  61. assert isinstance(function, types.FunctionType), "fix that!"
  62. from rpython.annotator.policy import AnnotatorPolicy
  63. policy = AnnotatorPolicy()
  64. # make input arguments and set their type
  65. args_s = [self.typeannotation(t) for t in input_arg_types]
  66. # XXX hack
  67. annmodel.TLS.check_str_without_nul = (
  68. self.translator.config.translation.check_str_without_nul)
  69. flowgraph, inputs_s = self.get_call_parameters(function, args_s, policy)
  70. if main_entry_point:
  71. self.translator.entry_point_graph = flowgraph
  72. return self.build_graph_types(flowgraph, inputs_s, complete_now=complete_now)
  73. def get_call_parameters(self, function, args_s, policy):
  74. desc = self.bookkeeper.getdesc(function)
  75. prevpolicy = self.policy
  76. self.policy = policy
  77. self.bookkeeper.enter(None)
  78. try:
  79. return desc.get_call_parameters(args_s)
  80. finally:
  81. self.bookkeeper.leave()
  82. self.policy = prevpolicy
  83. def annotate_helper(self, function, args_s, policy=None):
  84. if policy is None:
  85. from rpython.annotator.policy import AnnotatorPolicy
  86. policy = AnnotatorPolicy()
  87. # XXX hack
  88. annmodel.TLS.check_str_without_nul = (
  89. self.translator.config.translation.check_str_without_nul)
  90. graph, inputcells = self.get_call_parameters(function, args_s, policy)
  91. self.build_graph_types(graph, inputcells, complete_now=False)
  92. self.complete_helpers(policy)
  93. return graph
  94. def complete_helpers(self, policy):
  95. saved = self.policy, self.added_blocks
  96. self.policy = policy
  97. try:
  98. self.added_blocks = {}
  99. self.complete()
  100. # invoke annotation simplifications for the new blocks
  101. self.simplify(block_subset=self.added_blocks)
  102. finally:
  103. self.policy, self.added_blocks = saved
  104. def build_graph_types(self, flowgraph, inputcells, complete_now=True):
  105. checkgraph(flowgraph)
  106. nbarg = len(flowgraph.getargs())
  107. assert len(inputcells) == nbarg # wrong number of args
  108. # register the entry point
  109. self.addpendinggraph(flowgraph, inputcells)
  110. # recursively proceed until no more pending block is left
  111. if complete_now:
  112. self.complete()
  113. return self.annotation(flowgraph.getreturnvar())
  114. def gettype(self, variable):
  115. """Return the known type of a control flow graph variable,
  116. defaulting to 'object'."""
  117. if isinstance(variable, Constant):
  118. return type(variable.value)
  119. elif isinstance(variable, Variable):
  120. s_variable = variable.annotation
  121. if s_variable:
  122. return s_variable.knowntype
  123. else:
  124. return object
  125. else:
  126. raise TypeError("Variable or Constant instance expected, "
  127. "got %r" % (variable,))
  128. def getuserclassdefinitions(self):
  129. """Return a list of ClassDefs."""
  130. return self.bookkeeper.classdefs
  131. #___ medium-level interface ____________________________
  132. def addpendinggraph(self, flowgraph, inputcells):
  133. self.addpendingblock(flowgraph, flowgraph.startblock, inputcells)
  134. def addpendingblock(self, graph, block, cells):
  135. """Register an entry point into block with the given input cells."""
  136. if graph in self.fixed_graphs:
  137. # special case for annotating/rtyping in several phases: calling
  138. # a graph that has already been rtyped. Safety-check the new
  139. # annotations that are passed in, and don't annotate the old
  140. # graph -- it's already low-level operations!
  141. for a, s_newarg in zip(block.inputargs, cells):
  142. s_oldarg = self.binding(a)
  143. assert annmodel.unionof(s_oldarg, s_newarg) == s_oldarg
  144. else:
  145. assert not self.frozen
  146. if block not in self.annotated:
  147. self.bindinputargs(graph, block, cells)
  148. else:
  149. self.mergeinputargs(graph, block, cells)
  150. if not self.annotated[block]:
  151. self.pendingblocks[block] = graph
  152. def complete_pending_blocks(self):
  153. while self.pendingblocks:
  154. block, graph = self.pendingblocks.popitem()
  155. self.processblock(graph, block)
  156. def complete(self):
  157. """Process pending blocks until none is left."""
  158. while True:
  159. self.complete_pending_blocks()
  160. self.policy.no_more_blocks_to_annotate(self)
  161. if not self.pendingblocks:
  162. break # finished
  163. # make sure that the return variables of all graphs is annotated
  164. if self.added_blocks is not None:
  165. newgraphs = [self.annotated[block] for block in self.added_blocks]
  166. newgraphs = dict.fromkeys(newgraphs)
  167. got_blocked_blocks = False in newgraphs
  168. else:
  169. newgraphs = self.translator.graphs #all of them
  170. got_blocked_blocks = False in self.annotated.values()
  171. if got_blocked_blocks:
  172. for graph in self.blocked_graphs.values():
  173. self.blocked_graphs[graph] = True
  174. blocked_blocks = [block for block, done in self.annotated.items()
  175. if done is False]
  176. assert len(blocked_blocks) == len(self.blocked_blocks)
  177. text = format_blocked_annotation_error(self, self.blocked_blocks)
  178. #raise SystemExit()
  179. raise annmodel.AnnotatorError(text)
  180. for graph in newgraphs:
  181. v = graph.getreturnvar()
  182. if v.annotation is None:
  183. self.setbinding(v, s_ImpossibleValue)
  184. def validate(self):
  185. """Check that the annotation results are valid"""
  186. self.bookkeeper.check_no_flags_on_instances()
  187. def annotation(self, arg):
  188. "Gives the SomeValue corresponding to the given Variable or Constant."
  189. if isinstance(arg, Variable):
  190. return arg.annotation
  191. elif isinstance(arg, Constant):
  192. return self.bookkeeper.immutablevalue(arg.value)
  193. else:
  194. raise TypeError('Variable or Constant expected, got %r' % (arg,))
  195. def binding(self, arg):
  196. "Gives the SomeValue corresponding to the given Variable or Constant."
  197. s_arg = self.annotation(arg)
  198. if s_arg is None:
  199. raise KeyError
  200. return s_arg
  201. def typeannotation(self, t):
  202. return signature.annotation(t, self.bookkeeper)
  203. def setbinding(self, arg, s_value):
  204. s_old = arg.annotation
  205. if s_old is not None:
  206. if not s_value.contains(s_old):
  207. log.WARNING("%s does not contain %s" % (s_value, s_old))
  208. log.WARNING("%s" % annmodel.unionof(s_value, s_old))
  209. assert False
  210. arg.annotation = s_value
  211. def warning(self, msg, pos=None):
  212. if pos is None:
  213. try:
  214. pos = self.bookkeeper.position_key
  215. except AttributeError:
  216. pos = '?'
  217. if pos != '?':
  218. pos = self.whereami(pos)
  219. log.WARNING("%s/ %s" % (pos, msg))
  220. #___ interface for annotator.bookkeeper _______
  221. def recursivecall(self, graph, whence, inputcells):
  222. if isinstance(whence, tuple):
  223. parent_graph, parent_block, parent_index = whence
  224. tag = parent_block, parent_index
  225. self.translator.update_call_graph(parent_graph, graph, tag)
  226. # self.notify[graph.returnblock] is a dictionary of call
  227. # points to this func which triggers a reflow whenever the
  228. # return block of this graph has been analysed.
  229. callpositions = self.notify.setdefault(graph.returnblock, {})
  230. if whence is not None:
  231. if callable(whence):
  232. def callback():
  233. whence(self, graph)
  234. else:
  235. callback = whence
  236. callpositions[callback] = True
  237. # generalize the function's input arguments
  238. self.addpendingblock(graph, graph.startblock, inputcells)
  239. # get the (current) return value
  240. v = graph.getreturnvar()
  241. try:
  242. return self.binding(v)
  243. except KeyError:
  244. # the function didn't reach any return statement so far.
  245. # (some functions actually never do, they always raise exceptions)
  246. return s_ImpossibleValue
  247. def reflowfromposition(self, position_key):
  248. graph, block, index = position_key
  249. self.reflowpendingblock(graph, block)
  250. def call_sites(self):
  251. newblocks = self.added_blocks
  252. if newblocks is None:
  253. newblocks = self.annotated # all of them
  254. for block in newblocks:
  255. for op in block.operations:
  256. if op.opname in ('simple_call', 'call_args'):
  257. yield op
  258. # some blocks are partially annotated
  259. if op.result.annotation is None:
  260. break # ignore the unannotated part
  261. #___ simplification (should be moved elsewhere?) _______
  262. def simplify(self, block_subset=None, extra_passes=None):
  263. # Generic simplifications
  264. transform.transform_graph(self, block_subset=block_subset,
  265. extra_passes=extra_passes)
  266. if block_subset is None:
  267. graphs = self.translator.graphs
  268. else:
  269. graphs = {}
  270. for block in block_subset:
  271. graph = self.annotated.get(block)
  272. if graph:
  273. graphs[graph] = True
  274. for graph in graphs:
  275. simplify.eliminate_empty_blocks(graph)
  276. self.bookkeeper.compute_at_fixpoint()
  277. if block_subset is None:
  278. perform_normalizations(self)
  279. #___ flowing annotations in blocks _____________________
  280. def processblock(self, graph, block):
  281. # Important: this is not called recursively.
  282. # self.flowin() can only issue calls to self.addpendingblock().
  283. # The analysis of a block can be in three states:
  284. # * block not in self.annotated:
  285. # never seen the block.
  286. # * self.annotated[block] == False:
  287. # the input variables of the block have bindings but we
  288. # still have to consider all the operations in the block.
  289. # * self.annotated[block] == graph-containing-block:
  290. # analysis done (at least until we find we must generalize the
  291. # input variables).
  292. #print '* processblock', block, cells
  293. self.annotated[block] = graph
  294. if block in self.blocked_blocks:
  295. del self.blocked_blocks[block]
  296. try:
  297. self.flowin(graph, block)
  298. except BlockedInference as e:
  299. self.annotated[block] = False # failed, hopefully temporarily
  300. self.blocked_blocks[block] = (graph, e.opindex)
  301. except Exception as e:
  302. # hack for debug tools only
  303. if not hasattr(e, '__annotator_block'):
  304. setattr(e, '__annotator_block', block)
  305. raise
  306. # The dict 'added_blocks' is used by rpython.annlowlevel to
  307. # detect which are the new blocks that annotating an additional
  308. # small helper creates.
  309. if self.added_blocks is not None:
  310. self.added_blocks[block] = True
  311. def reflowpendingblock(self, graph, block):
  312. assert not self.frozen
  313. assert graph not in self.fixed_graphs
  314. self.pendingblocks[block] = graph
  315. assert block in self.annotated
  316. self.annotated[block] = False # must re-flow
  317. self.blocked_blocks[block] = (graph, None)
  318. def bindinputargs(self, graph, block, inputcells):
  319. # Create the initial bindings for the input args of a block.
  320. assert len(block.inputargs) == len(inputcells)
  321. for a, cell in zip(block.inputargs, inputcells):
  322. self.setbinding(a, cell)
  323. self.annotated[block] = False # must flowin.
  324. self.blocked_blocks[block] = (graph, None)
  325. def mergeinputargs(self, graph, block, inputcells):
  326. # Merge the new 'cells' with each of the block's existing input
  327. # variables.
  328. oldcells = [self.binding(a) for a in block.inputargs]
  329. try:
  330. unions = [annmodel.unionof(c1,c2) for c1, c2 in zip(oldcells,inputcells)]
  331. except annmodel.UnionError as e:
  332. # Add source code to the UnionError
  333. e.source = '\n'.join(source_lines(graph, block, None, long=True))
  334. raise
  335. # if the merged cells changed, we must redo the analysis
  336. if unions != oldcells:
  337. self.bindinputargs(graph, block, unions)
  338. def apply_renaming(self, s_out, renaming):
  339. if hasattr(s_out, 'is_type_of'):
  340. renamed_is_type_of = []
  341. for v in s_out.is_type_of:
  342. renamed_is_type_of += renaming[v]
  343. assert s_out.knowntype is type
  344. newcell = typeof(renamed_is_type_of)
  345. if s_out.is_constant():
  346. newcell.const = s_out.const
  347. s_out = newcell
  348. if hasattr(s_out, 'knowntypedata'):
  349. renamed_knowntypedata = {}
  350. for value, constraints in s_out.knowntypedata.items():
  351. renamed_knowntypedata[value] = {}
  352. for v, s in constraints.items():
  353. new_vs = renaming.get(v, [])
  354. for new_v in new_vs:
  355. renamed_knowntypedata[value][new_v] = s
  356. assert isinstance(s_out, annmodel.SomeBool)
  357. newcell = annmodel.SomeBool()
  358. if s_out.is_constant():
  359. newcell.const = s_out.const
  360. s_out = newcell
  361. s_out.set_knowntypedata(renamed_knowntypedata)
  362. return s_out
  363. def whereami(self, position_key):
  364. graph, block, i = position_key
  365. blk = ""
  366. if block:
  367. at = block.at()
  368. if at:
  369. blk = " block"+at
  370. opid=""
  371. if i is not None:
  372. opid = " op=%d" % i
  373. return repr(graph) + blk + opid
  374. def flowin(self, graph, block):
  375. try:
  376. i = 0
  377. while i < len(block.operations):
  378. op = block.operations[i]
  379. with self.bookkeeper.at_position((graph, block, i)):
  380. new_ops = op.transform(self)
  381. if new_ops is not None:
  382. block.operations[i:i+1] = new_ops
  383. if not new_ops:
  384. continue
  385. new_ops[-1].result = op.result
  386. op = new_ops[0]
  387. self.consider_op(op)
  388. i += 1
  389. except BlockedInference as e:
  390. if e.op is block.raising_op:
  391. # this is the case where the last operation of the block will
  392. # always raise an exception which is immediately caught by
  393. # an exception handler. We then only follow the exceptional
  394. # branches.
  395. exits = [link for link in block.exits
  396. if link.exitcase is not None]
  397. elif e.op.opname in ('simple_call', 'call_args', 'next'):
  398. # XXX warning, keep the name of the call operations in sync
  399. # with the flow object space. These are the operations for
  400. # which it is fine to always raise an exception. We then
  401. # swallow the BlockedInference and that's it.
  402. # About 'next': see test_annotate_iter_empty_container().
  403. return
  404. else:
  405. # other cases are problematic (but will hopefully be solved
  406. # later by reflowing). Throw the BlockedInference up to
  407. # processblock().
  408. e.opindex = i
  409. raise
  410. except annmodel.HarmlesslyBlocked:
  411. return
  412. except annmodel.AnnotatorError as e: # note that UnionError is a subclass
  413. e.source = gather_error(self, graph, block, i)
  414. raise
  415. else:
  416. # dead code removal: don't follow all exits if the exitswitch
  417. # is known
  418. exits = block.exits
  419. if isinstance(block.exitswitch, Variable):
  420. s_exitswitch = self.binding(block.exitswitch)
  421. if s_exitswitch.is_constant():
  422. exits = [link for link in exits
  423. if link.exitcase == s_exitswitch.const]
  424. if block.canraise:
  425. op = block.raising_op
  426. s_exception = self.get_exception(op)
  427. for link in exits:
  428. case = link.exitcase
  429. if case is None:
  430. self.follow_link(graph, link, {})
  431. continue
  432. if s_exception == s_ImpossibleValue:
  433. break
  434. s_case = SomeInstance(self.bookkeeper.getuniqueclassdef(case))
  435. s_matching_exc = intersection(s_exception, s_case)
  436. if s_matching_exc != s_ImpossibleValue:
  437. self.follow_raise_link(graph, link, s_matching_exc)
  438. s_exception = difference(s_exception, s_case)
  439. else:
  440. if isinstance(block.exitswitch, Variable):
  441. knowntypedata = getattr(
  442. block.exitswitch.annotation, "knowntypedata", {})
  443. else:
  444. knowntypedata = {}
  445. for link in exits:
  446. constraints = knowntypedata.get(link.exitcase, {})
  447. self.follow_link(graph, link, constraints)
  448. if block in self.notify:
  449. # reflow from certain positions when this block is done
  450. for callback in self.notify[block]:
  451. if isinstance(callback, tuple):
  452. self.reflowfromposition(callback) # callback is a position
  453. else:
  454. callback()
  455. def follow_link(self, graph, link, constraints):
  456. assert not (isinstance(link.exitcase, (types.ClassType, type)) and
  457. issubclass(link.exitcase, BaseException))
  458. ignore_link = False
  459. inputs_s = []
  460. renaming = defaultdict(list)
  461. for v_out, v_input in zip(link.args, link.target.inputargs):
  462. renaming[v_out].append(v_input)
  463. for v_out in link.args:
  464. s_out = self.annotation(v_out)
  465. if v_out in constraints:
  466. s_constraint = constraints[v_out]
  467. s_out = pair(s_out, s_constraint).improve()
  468. # ignore links that try to pass impossible values
  469. if s_out == s_ImpossibleValue:
  470. ignore_link = True
  471. s_out = self.apply_renaming(s_out, renaming)
  472. inputs_s.append(s_out)
  473. if ignore_link:
  474. return
  475. self.links_followed[link] = True
  476. self.addpendingblock(graph, link.target, inputs_s)
  477. def follow_raise_link(self, graph, link, s_last_exc_value):
  478. v_last_exc_type = link.last_exception
  479. v_last_exc_value = link.last_exc_value
  480. assert (isinstance(link.exitcase, (types.ClassType, type)) and
  481. issubclass(link.exitcase, BaseException))
  482. assert v_last_exc_type and v_last_exc_value
  483. if isinstance(v_last_exc_value, Variable):
  484. self.setbinding(v_last_exc_value, s_last_exc_value)
  485. if isinstance(v_last_exc_type, Variable):
  486. self.setbinding(v_last_exc_type, typeof([v_last_exc_value]))
  487. inputs_s = []
  488. renaming = defaultdict(list)
  489. for v_out, v_input in zip(link.args, link.target.inputargs):
  490. renaming[v_out].append(v_input)
  491. for v_out, v_input in zip(link.args, link.target.inputargs):
  492. if v_out == v_last_exc_type:
  493. s_out = typeof(renaming[v_last_exc_value])
  494. if isinstance(v_last_exc_type, Constant):
  495. s_out.const = v_last_exc_type.value
  496. elif v_last_exc_type.annotation.is_constant():
  497. s_out.const = v_last_exc_type.annotation.const
  498. inputs_s.append(s_out)
  499. else:
  500. s_out = self.annotation(v_out)
  501. s_out = self.apply_renaming(s_out, renaming)
  502. inputs_s.append(s_out)
  503. self.links_followed[link] = True
  504. self.addpendingblock(graph, link.target, inputs_s)
  505. #___ creating the annotations based on operations ______
  506. def consider_op(self, op):
  507. # let's be careful about avoiding propagated SomeImpossibleValues
  508. # to enter an op; the latter can result in violations of the
  509. # more general results invariant: e.g. if SomeImpossibleValue enters is_
  510. # is_(SomeImpossibleValue, None) -> SomeBool
  511. # is_(SomeInstance(not None), None) -> SomeBool(const=False) ...
  512. # boom -- in the assert of setbinding()
  513. for arg in op.args:
  514. if isinstance(self.annotation(arg), annmodel.SomeImpossibleValue):
  515. raise BlockedInference(self, op, -1)
  516. resultcell = op.consider(self)
  517. if resultcell is None:
  518. resultcell = s_ImpossibleValue
  519. elif resultcell == s_ImpossibleValue:
  520. raise BlockedInference(self, op, -1) # the operation cannot succeed
  521. assert isinstance(resultcell, annmodel.SomeObject)
  522. assert isinstance(op.result, Variable)
  523. self.setbinding(op.result, resultcell) # bind resultcell to op.result
  524. def get_exception(self, operation):
  525. """
  526. Return the annotation for all exceptions that `operation` may raise.
  527. """
  528. can_only_throw = operation.get_can_only_throw(self)
  529. if can_only_throw is None:
  530. return SomeInstance(self.bookkeeper.getuniqueclassdef(Exception))
  531. else:
  532. return self.bookkeeper.new_exception(can_only_throw)
  533. class BlockedInference(Exception):
  534. """This exception signals the type inference engine that the situation
  535. is currently blocked, and that it should try to progress elsewhere."""
  536. def __init__(self, annotator, op, opindex):
  537. self.annotator = annotator
  538. try:
  539. self.break_at = annotator.bookkeeper.position_key
  540. except AttributeError:
  541. self.break_at = None
  542. self.op = op
  543. self.opindex = opindex
  544. def __repr__(self):
  545. if not self.break_at:
  546. break_at = "?"
  547. else:
  548. break_at = self.annotator.whereami(self.break_at)
  549. return "<BlockedInference break_at %s [%s]>" %(break_at, self.op)
  550. __str__ = __repr__