PageRenderTime 85ms CodeModel.GetById 16ms RepoModel.GetById 1ms app.codeStats 0ms

/pypy/translator/transform.py

https://github.com/lalitjsraks/pypy
Python | 273 lines | 197 code | 24 blank | 52 comment | 48 complexity | 58005c721be1770d221242688b339cd9 MD5 | raw file
  1. """Flow Graph Transformation
  2. The difference between simplification and transformation is that
  3. transformation is based on annotations; it runs after the annotator
  4. completed.
  5. """
  6. import types
  7. from pypy.objspace.flow.model import SpaceOperation
  8. from pypy.objspace.flow.model import Variable, Constant, Link
  9. from pypy.objspace.flow.model import c_last_exception, checkgraph
  10. from pypy.annotation import model as annmodel
  11. from pypy.rlib.rstack import stack_check
  12. from pypy.rpython.lltypesystem import lltype
  13. def checkgraphs(self, blocks):
  14. seen = {}
  15. for block in blocks:
  16. graph = self.annotated[block]
  17. if graph not in seen:
  18. checkgraph(graph)
  19. seen[graph] = True
  20. def fully_annotated_blocks(self):
  21. """Ignore blocked blocks."""
  22. for block, is_annotated in self.annotated.iteritems():
  23. if is_annotated:
  24. yield block
  25. # XXX: Lots of duplicated codes. Fix this!
  26. # [a] * b
  27. # -->
  28. # c = newlist(a)
  29. # d = mul(c, int b)
  30. # -->
  31. # d = alloc_and_set(b, a)
  32. def transform_allocate(self, block_subset):
  33. """Transforms [a] * b to alloc_and_set(b, a) where b is int."""
  34. for block in block_subset:
  35. length1_lists = {} # maps 'c' to 'a', in the above notation
  36. for i in range(len(block.operations)):
  37. op = block.operations[i]
  38. if (op.opname == 'newlist' and
  39. len(op.args) == 1):
  40. length1_lists[op.result] = op.args[0]
  41. elif (op.opname == 'mul' and
  42. op.args[0] in length1_lists and
  43. self.gettype(op.args[1]) is int):
  44. new_op = SpaceOperation('alloc_and_set',
  45. (op.args[1], length1_lists[op.args[0]]),
  46. op.result)
  47. block.operations[i] = new_op
  48. # lst += string[x:y]
  49. # -->
  50. # b = getslice(string, x, y)
  51. # c = inplace_add(lst, b)
  52. # -->
  53. # c = extend_with_str_slice(lst, x, y, string)
  54. def transform_extend_with_str_slice(self, block_subset):
  55. """Transforms lst += string[x:y] to extend_with_str_slice"""
  56. for block in block_subset:
  57. slice_sources = {} # maps b to [string, slice] in the above notation
  58. for i in range(len(block.operations)):
  59. op = block.operations[i]
  60. if (op.opname == 'getslice' and
  61. self.gettype(op.args[0]) is str):
  62. slice_sources[op.result] = op.args
  63. elif (op.opname == 'inplace_add' and
  64. op.args[1] in slice_sources and
  65. self.gettype(op.args[0]) is list):
  66. v_string, v_x, v_y = slice_sources[op.args[1]]
  67. new_op = SpaceOperation('extend_with_str_slice',
  68. [op.args[0], v_x, v_y, v_string],
  69. op.result)
  70. block.operations[i] = new_op
  71. # lst += char*count [or count*char]
  72. # -->
  73. # b = mul(char, count) [or count, char]
  74. # c = inplace_add(lst, b)
  75. # -->
  76. # c = extend_with_char_count(lst, char, count)
  77. def transform_extend_with_char_count(self, block_subset):
  78. """Transforms lst += char*count to extend_with_char_count"""
  79. for block in block_subset:
  80. mul_sources = {} # maps b to (char, count) in the above notation
  81. for i in range(len(block.operations)):
  82. op = block.operations[i]
  83. if op.opname == 'mul':
  84. s0 = self.binding(op.args[0], None)
  85. s1 = self.binding(op.args[1], None)
  86. if (isinstance(s0, annmodel.SomeChar) and
  87. isinstance(s1, annmodel.SomeInteger)):
  88. mul_sources[op.result] = op.args[0], op.args[1]
  89. elif (isinstance(s1, annmodel.SomeChar) and
  90. isinstance(s0, annmodel.SomeInteger)):
  91. mul_sources[op.result] = op.args[1], op.args[0]
  92. elif (op.opname == 'inplace_add' and
  93. op.args[1] in mul_sources and
  94. self.gettype(op.args[0]) is list):
  95. v_char, v_count = mul_sources[op.args[1]]
  96. new_op = SpaceOperation('extend_with_char_count',
  97. [op.args[0], v_char, v_count],
  98. op.result)
  99. block.operations[i] = new_op
  100. def transform_dead_op_vars(self, block_subset):
  101. # we redo the same simplification from simplify.py,
  102. # to kill dead (never-followed) links,
  103. # which can possibly remove more variables.
  104. from pypy.translator.simplify import transform_dead_op_vars_in_blocks
  105. transform_dead_op_vars_in_blocks(block_subset)
  106. def transform_dead_code(self, block_subset):
  107. """Remove dead code: these are the blocks that are not annotated at all
  108. because the annotation considered that no conditional jump could reach
  109. them."""
  110. for block in block_subset:
  111. for link in block.exits:
  112. if link not in self.links_followed:
  113. lst = list(block.exits)
  114. lst.remove(link)
  115. block.exits = tuple(lst)
  116. if not block.exits:
  117. # oups! cannot reach the end of this block
  118. cutoff_alwaysraising_block(self, block)
  119. elif block.exitswitch == c_last_exception:
  120. # exceptional exit
  121. if block.exits[0].exitcase is not None:
  122. # killed the non-exceptional path!
  123. cutoff_alwaysraising_block(self, block)
  124. if len(block.exits) == 1:
  125. block.exitswitch = None
  126. block.exits[0].exitcase = None
  127. def cutoff_alwaysraising_block(self, block):
  128. "Fix a block whose end can never be reached at run-time."
  129. # search the operation that cannot succeed
  130. can_succeed = [op for op in block.operations
  131. if op.result in self.bindings]
  132. cannot_succeed = [op for op in block.operations
  133. if op.result not in self.bindings]
  134. n = len(can_succeed)
  135. # check consistency
  136. assert can_succeed == block.operations[:n]
  137. assert cannot_succeed == block.operations[n:]
  138. assert 0 <= n < len(block.operations)
  139. # chop off the unreachable end of the block
  140. del block.operations[n+1:]
  141. s_impossible = annmodel.SomeImpossibleValue()
  142. self.bindings[block.operations[n].result] = s_impossible
  143. # insert the equivalent of 'raise AssertionError'
  144. graph = self.annotated[block]
  145. msg = "Call to %r should have raised an exception" % (getattr(graph, 'func', None),)
  146. c1 = Constant(AssertionError)
  147. c2 = Constant(AssertionError(msg))
  148. errlink = Link([c1, c2], graph.exceptblock)
  149. block.recloseblock(errlink, *block.exits)
  150. # record new link to make the transformation idempotent
  151. self.links_followed[errlink] = True
  152. # fix the annotation of the exceptblock.inputargs
  153. etype, evalue = graph.exceptblock.inputargs
  154. s_type = annmodel.SomeObject()
  155. s_type.knowntype = type
  156. s_type.is_type_of = [evalue]
  157. s_value = annmodel.SomeInstance(self.bookkeeper.getuniqueclassdef(Exception))
  158. self.setbinding(etype, s_type)
  159. self.setbinding(evalue, s_value)
  160. # make sure the bookkeeper knows about AssertionError
  161. self.bookkeeper.getuniqueclassdef(AssertionError)
  162. def insert_stackcheck(ann):
  163. from pypy.tool.algo.graphlib import Edge, make_edge_dict, break_cycles
  164. edges = []
  165. graphs_to_patch = {}
  166. for callposition, (caller, callee) in ann.translator.callgraph.items():
  167. if getattr(getattr(callee, 'func', None), 'insert_stack_check_here', False):
  168. graphs_to_patch[callee] = True
  169. continue
  170. edge = Edge(caller, callee)
  171. edge.callposition = callposition
  172. edges.append(edge)
  173. for graph in graphs_to_patch:
  174. v = Variable()
  175. ann.setbinding(v, annmodel.SomeImpossibleValue())
  176. unwind_op = SpaceOperation('simple_call', [Constant(stack_check)], v)
  177. graph.startblock.operations.insert(0, unwind_op)
  178. edgedict = make_edge_dict(edges)
  179. for edge in break_cycles(edgedict, edgedict):
  180. caller = edge.source
  181. _, _, call_tag = edge.callposition
  182. if call_tag:
  183. caller_block, _ = call_tag
  184. else:
  185. ann.warning("cycle detected but no information on where to insert "
  186. "stack_check()")
  187. continue
  188. # caller block found, insert stack_check()
  189. v = Variable()
  190. # push annotation on v
  191. ann.setbinding(v, annmodel.SomeImpossibleValue())
  192. unwind_op = SpaceOperation('simple_call', [Constant(stack_check)], v)
  193. caller_block.operations.insert(0, unwind_op)
  194. def insert_ll_stackcheck(translator):
  195. from pypy.translator.backendopt.support import find_calls_from
  196. from pypy.rlib.rstack import stack_check
  197. from pypy.tool.algo.graphlib import Edge, make_edge_dict, break_cycles_v
  198. rtyper = translator.rtyper
  199. graph = rtyper.annotate_helper(stack_check, [])
  200. rtyper.specialize_more_blocks()
  201. stack_check_ptr = rtyper.getcallable(graph)
  202. stack_check_ptr_const = Constant(stack_check_ptr, lltype.typeOf(stack_check_ptr))
  203. edges = set()
  204. insert_in = set()
  205. for caller in translator.graphs:
  206. for block, callee in find_calls_from(translator, caller):
  207. if getattr(getattr(callee, 'func', None),
  208. 'insert_stack_check_here', False):
  209. insert_in.add(callee.startblock)
  210. continue
  211. if block is not caller.startblock:
  212. edges.add((caller.startblock, block))
  213. edges.add((block, callee.startblock))
  214. edgelist = [Edge(block1, block2) for (block1, block2) in edges]
  215. edgedict = make_edge_dict(edgelist)
  216. for block in break_cycles_v(edgedict, edgedict):
  217. insert_in.add(block)
  218. for block in insert_in:
  219. v = Variable()
  220. v.concretetype = lltype.Void
  221. unwind_op = SpaceOperation('direct_call', [stack_check_ptr_const], v)
  222. block.operations.insert(0, unwind_op)
  223. return len(insert_in)
  224. default_extra_passes = [
  225. transform_allocate,
  226. transform_extend_with_str_slice,
  227. transform_extend_with_char_count,
  228. ]
  229. def transform_graph(ann, extra_passes=None, block_subset=None):
  230. """Apply set of transformations available."""
  231. # WARNING: this produces incorrect results if the graph has been
  232. # modified by t.simplify() after it had been annotated.
  233. if extra_passes is None:
  234. extra_passes = default_extra_passes
  235. if block_subset is None:
  236. block_subset = fully_annotated_blocks(ann)
  237. if not isinstance(block_subset, dict):
  238. block_subset = dict.fromkeys(block_subset)
  239. if ann.translator:
  240. checkgraphs(ann, block_subset)
  241. transform_dead_code(ann, block_subset)
  242. for pass_ in extra_passes:
  243. pass_(ann, block_subset)
  244. # do this last, after the previous transformations had a
  245. # chance to remove dependency on certain variables
  246. transform_dead_op_vars(ann, block_subset)
  247. if ann.translator:
  248. checkgraphs(ann, block_subset)