cfg_to_tree.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379
  1. """Lowers CFG-IR to tree-IR."""
  2. import modelverse_jit.cfg_ir as cfg_ir
  3. import modelverse_jit.cfg_optimization as cfg_optimization
  4. import modelverse_jit.tree_ir as tree_ir
  5. import modelverse_jit.runtime as jit_runtime
  6. # The CFG deconstruction code here is based on the relooper algorithm
  7. # as detailed in https://github.com/kripken/Relooper/blob/master/paper.pdf
  8. class FlowGraphComponent(object):
  9. """Represents a control-flow graph component."""
  10. def __init__(self, entry_blocks, blocks, reachable):
  11. self.entry_blocks = entry_blocks
  12. self.blocks = blocks
  13. self.reachable = reachable
  14. def get_entry_reachable_blocks(self):
  15. """Gets the set of all blocks that are reachable from the entry points."""
  16. return set.union(*[self.get_reachable_blocks(block) for block in self.entry_blocks])
  17. def get_reachable_blocks(self, block):
  18. """Gets all blocks in this component that are reachable from the given block."""
  19. return set.intersection(self.reachable[block], self.blocks)
  20. def get_directly_reachable_blocks(self, block):
  21. """Gets all blocks in this component that are reachable from the given block by taking
  22. exactly one branch."""
  23. return set.intersection(cfg_optimization.get_directly_reachable_blocks(block), self.blocks)
  24. def can_reach(self, source_block, target_block):
  25. """Tests if the first block can reach the second."""
  26. return target_block in self.reachable[source_block] and target_block in self.blocks
  27. def sub_component(self, new_entry_points):
  28. """Creates a sub-component of this component, with the given new entry points."""
  29. result = FlowGraphComponent(new_entry_points, self.blocks, self.reachable)
  30. result.blocks = result.get_entry_reachable_blocks()
  31. return result
  32. class SimpleBlock(object):
  33. """A 'simple' block in the relooper algorithm."""
  34. def __init__(self, body, next_block):
  35. self.body = body
  36. self.next_block = next_block
  37. def lower(self, state):
  38. """Lowers this 'simple' block to a tree."""
  39. return tree_ir.create_block(
  40. state.lower_block(self.body),
  41. self.next_block.lower(state))
  42. class LoopBlock(object):
  43. """A 'loop' block in the relooper algorithm."""
  44. def __init__(self, inner, next_block):
  45. self.inner = inner
  46. self.next_block = next_block
  47. def lower(self, state):
  48. """Lowers this 'loop' block to a tree."""
  49. old_loop = state.current_loop
  50. state.current_loop = self
  51. inner = tree_ir.LoopInstruction(
  52. tree_ir.create_block(
  53. self.inner.lower(state),
  54. tree_ir.BreakInstruction()))
  55. state.current_loop = old_loop
  56. return tree_ir.create_block(
  57. inner,
  58. self.next_block.lower(state))
  59. class MultipleBlock(object):
  60. """A 'multiple' block in the relooper algorithm that does _not_ require a loop."""
  61. def __init__(self, handled_blocks, next_block):
  62. self.handled_blocks = handled_blocks
  63. self.next_block = next_block
  64. def lower_handled_blocks(self, state):
  65. """Lowers the handled blocks of this 'multiple' block to a tree."""
  66. result = tree_ir.EmptyInstruction()
  67. for entry, block in self.handled_blocks:
  68. result = tree_ir.SelectInstruction(
  69. tree_ir.BinaryInstruction(
  70. state.label_variable,
  71. '==',
  72. tree_ir.LiteralInstruction(entry.index)),
  73. block.lower(state),
  74. result)
  75. return result
  76. def lower(self, state):
  77. """Lowers this 'multiple' block to a tree."""
  78. return tree_ir.create_block(
  79. self.lower_handled_blocks(state),
  80. self.next_block.lower(state))
  81. class MultipleLoopBlock(MultipleBlock):
  82. """A 'multiple' block in the relooper algorithm."""
  83. def __init__(self, handled_blocks, next_block):
  84. MultipleBlock.__init__(self, handled_blocks, next_block)
  85. def lower(self, state):
  86. """Lowers this 'multiple' block to a tree."""
  87. old_loop = state.current_loop
  88. state.current_loop = self
  89. inner = tree_ir.LoopInstruction(
  90. tree_ir.create_block(
  91. self.lower_handled_blocks(state),
  92. tree_ir.BreakInstruction()))
  93. state.current_loop = old_loop
  94. return tree_ir.create_block(
  95. inner,
  96. self.next_block.lower(state))
  97. class ContinueFlow(cfg_ir.FlowInstruction):
  98. """Represents a control flow instruction which continues to the next loop iteration."""
  99. def __init__(self, loop):
  100. cfg_ir.FlowInstruction.__init__(self)
  101. self.loop = loop
  102. def get_dependencies(self):
  103. """Gets all definitions and instructions on which this instruction depends."""
  104. return []
  105. def branches(self):
  106. """Gets a list of basic blocks targeted by this flow instruction."""
  107. return []
  108. def __str__(self):
  109. return 'continue'
  110. class BreakFlow(cfg_ir.FlowInstruction):
  111. """Represents a control flow instruction which breaks out of a loop."""
  112. def __init__(self, loop):
  113. cfg_ir.FlowInstruction.__init__(self)
  114. self.loop = loop
  115. def get_dependencies(self):
  116. """Gets all definitions and instructions on which this instruction depends."""
  117. return []
  118. def branches(self):
  119. """Gets a list of basic blocks targeted by this flow instruction."""
  120. return []
  121. def __str__(self):
  122. return 'break'
  123. def solipsize(graph_component, loop, entry_blocks, next_blocks):
  124. """Replaces branches to entry blocks and next blocks by jumps to 'continue' and 'break'
  125. blocks, respectively."""
  126. all_blocks = set()
  127. reachable_from_inner = set()
  128. for block in graph_component.blocks:
  129. if block in next_blocks:
  130. continue
  131. all_blocks.add(block)
  132. for branch in block.flow.branches():
  133. if branch.block in entry_blocks:
  134. # Create a 'continue' block, and point to that.
  135. continue_block = cfg_ir.BasicBlock(block.counter)
  136. for param in branch.block.parameters:
  137. continue_block.append_parameter(param)
  138. continue_block.flow = ContinueFlow(loop)
  139. branch.block = continue_block
  140. all_blocks.add(continue_block)
  141. elif branch.block in next_blocks:
  142. # Create a 'break' block, and point to that.
  143. break_block = cfg_ir.BasicBlock(block.counter)
  144. for param in branch.block.parameters:
  145. break_block.append_parameter(param)
  146. break_block.flow = BreakFlow(loop)
  147. branch.block = break_block
  148. all_blocks.add(break_block)
  149. reachable_from_inner.add(branch.block)
  150. return reachable_from_inner, FlowGraphComponent(
  151. graph_component.entry_blocks,
  152. all_blocks,
  153. {
  154. block : cfg_optimization.get_reachable_blocks(block)
  155. for block in all_blocks
  156. })
  157. def to_relooper_loop(graph_component):
  158. """Converts the given graph component to a relooper 'loop'."""
  159. entry_blocks = graph_component.entry_blocks
  160. result = LoopBlock(None, None)
  161. inner_blocks = []
  162. next_blocks = []
  163. for block in graph_component.blocks:
  164. if any([graph_component.can_reach(block, ep) for ep in entry_blocks]):
  165. inner_blocks.append(block)
  166. else:
  167. next_blocks.append(block)
  168. reachable_from_inner, inner_component = solipsize(
  169. graph_component, result, entry_blocks, next_blocks)
  170. result.inner = reloop(inner_component)
  171. next_component = FlowGraphComponent(
  172. reachable_from_inner, next_blocks, graph_component.reachable)
  173. result.next_block = reloop(next_component)
  174. return result
  175. def to_relooper_multiple_or_loop(graph_component):
  176. """Converts the given graph component to a relooper 'multiple' or 'loop'."""
  177. # From the Emscripten paper:
  178. #
  179. # If we have more than one entry, try to create a Multiple block:
  180. # For each entry, find all the labels it reaches that
  181. # cannot be reached by any other entry. If at least one entry
  182. # has such labels, return a Multiple block, whose Handled
  183. # blocks are blocks for those labels (and whose entries are
  184. # those labels), and whose Next block is all the rest. Entries
  185. # for the next block are entries that did not become part of
  186. # the Handled blocks, and also labels that can be reached
  187. # from the Handled blocks.
  188. entry_blocks = graph_component.entry_blocks
  189. if len(entry_blocks) <= 1:
  190. return to_relooper_loop(graph_component)
  191. entry_reachables = {ep : graph_component.get_reachable_blocks(ep) for ep in entry_blocks}
  192. exclusive_entries = {}
  193. for entry in entry_blocks:
  194. exclusive_blocks = set(entry_reachables[entry])
  195. for other_entry in entry_blocks:
  196. if other_entry != entry:
  197. exclusive_blocks.difference_update(entry_reachables[other_entry])
  198. if len(exclusive_blocks) > 0:
  199. exclusive_entries[entry] = exclusive_blocks
  200. if len(exclusive_entries) == 0:
  201. return to_relooper_loop(graph_component)
  202. next_entries = set(graph_component.entry_blocks)
  203. for block_set in exclusive_entries.values():
  204. for elem in block_set:
  205. directly_reachable = graph_component.get_directly_reachable_blocks(elem)
  206. directly_reachable.remove(elem)
  207. next_entries.update(directly_reachable)
  208. next_entries.difference_update(exclusive_entries.keys())
  209. result = MultipleLoopBlock({}, None)
  210. for entry, exclusive_blocks in exclusive_entries.items():
  211. other_blocks = set(graph_component.blocks)
  212. other_blocks.difference_update(exclusive_blocks)
  213. result.handled_blocks[entry] = reloop(
  214. solipsize(graph_component, result, set([entry]), other_blocks))
  215. result.next_block = reloop(graph_component.sub_component(next_entries))
  216. return result
  217. def reloop(graph_component):
  218. """Applies the relooper algorithm to the given graph component."""
  219. reachable_set = graph_component.get_entry_reachable_blocks()
  220. entry_blocks = graph_component.entry_blocks
  221. if len(entry_blocks) == 1 and entry_blocks[0] not in reachable_set:
  222. graph_component.blocks.remove(entry_blocks[0])
  223. return SimpleBlock(
  224. entry_blocks[0],
  225. reloop(
  226. FlowGraphComponent(
  227. graph_component.get_directly_reachable_blocks(entry_blocks[0]),
  228. graph_component.blocks,
  229. graph_component.reachable)))
  230. elif all([block in reachable_set for block in entry_blocks]):
  231. return to_relooper_loop(graph_component)
  232. else:
  233. return to_relooper_multiple_or_loop(graph_component)
  234. class LoweringState(object):
  235. """Stores information related to the relooper->tree conversion."""
  236. def __init__(self, jit):
  237. self.jit = jit
  238. self.label_variable = tree_ir.VariableName('__label')
  239. self.definition_loads = {}
  240. def __create_value_load(self, value):
  241. """Creates a tree that loads the given value."""
  242. if value.has_value():
  243. if isinstance(value, cfg_ir.Literal):
  244. return tree_ir.LiteralInstruction(value.literal)
  245. else:
  246. return tree_ir.LoadLocalInstruction(None)
  247. else:
  248. return tree_ir.LiteralInstruction(None)
  249. def load_definition(self, definition):
  250. """Loads the given definition's variable."""
  251. if definition in self.definition_loads:
  252. return self.definition_loads[definition]
  253. result = self.__create_value_load(definition.value)
  254. self.definition_loads[definition] = result
  255. return result
  256. def lower_block(self, block):
  257. """Lowers the given (relooped) block to a tree."""
  258. statements = []
  259. for definition in block.definitions:
  260. statements.append(self.lower_definition(definition))
  261. statements.append(self.lower_flow(block.flow))
  262. return tree_ir.create_block(statements)
  263. def lower_definition(self, definition):
  264. """Lowers the given definition to a tree."""
  265. instruction = definition.value
  266. tree_instruction = self.lower_instruction(instruction)
  267. def_load = self.load_definition(definition)
  268. if isinstance(def_load, tree_ir.LocalInstruction):
  269. return def_load.create_store(tree_instruction)
  270. else:
  271. return tree_instruction
  272. def lower_instruction(self, instruction):
  273. """Lowers the given instruction to a tree."""
  274. raise NotImplementedError()
  275. def lower_flow(self, flow):
  276. """Lowers the given (relooped) flow instruction to a tree."""
  277. flow_type = type(flow)
  278. if flow_type in LoweringState.flow_lowerings:
  279. return LoweringState.flow_lowerings[flow_type](self, flow)
  280. else:
  281. raise jit_runtime.JitCompilationFailedException(
  282. "Unknown CFG flow instruction: '%s'" % flow)
  283. def lower_jump(self, flow):
  284. """Lowers the given 'jump' flow instruction to a tree."""
  285. return self.lower_branch(flow.branch)
  286. def lower_select(self, flow):
  287. """Lowers the given 'select' flow instruction to a tree."""
  288. return tree_ir.SelectInstruction(
  289. self.load_definition(flow.condition),
  290. self.lower_branch(flow.if_branch),
  291. self.lower_branch(flow.else_branch))
  292. def lower_return(self, flow):
  293. """Lowers the given 'return' flow instruction to a tree."""
  294. return tree_ir.ReturnInstruction(self.load_definition(flow.value))
  295. def lower_unreachable(self, _):
  296. """Lowers the given 'unreachable' flow instruction to a tree."""
  297. return tree_ir.EmptyInstruction()
  298. def lower_break(self, _):
  299. """Lowers the given 'break' flow instruction to a tree."""
  300. return tree_ir.BreakInstruction()
  301. def lower_continue(self, _):
  302. """Lowers the given 'continue' flow instruction to a tree."""
  303. return tree_ir.ContinueInstruction()
  304. def lower_branch(self, branch):
  305. """Lowers the given (relooped) branch to a tree."""
  306. for param, arg in zip(branch.block.parameters, branch.arguments):
  307. self.load_definition(param).create_store(self.load_definition(arg))
  308. return tree_ir.StoreLocalInstruction(
  309. self.label_variable,
  310. tree_ir.LiteralInstruction(branch.block.index))
  311. flow_lowerings = {
  312. cfg_ir.JumpFlow : lower_jump,
  313. cfg_ir.SelectFlow : lower_select,
  314. cfg_ir.ReturnFlow : lower_return,
  315. cfg_ir.UnreachableFlow : lower_unreachable,
  316. BreakFlow : lower_break,
  317. ContinueFlow : lower_continue
  318. }