cfg_to_tree.py 18 KB

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