cfg_optimization.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317
  1. """Optimizes and analyzes CFG-IR."""
  2. from collections import defaultdict
  3. import modelverse_jit.cfg_ir as cfg_ir
  4. import modelverse_jit.cfg_dominators as cfg_dominators
  5. def is_empty_block(block):
  6. """Tests if the given block contains no parameters or definitions."""
  7. return len(block.parameters) == 0 and len(block.definitions) == 0
  8. def optimize_flow(block):
  9. """Optimizes the given block's flow instruction."""
  10. changed = True
  11. while changed:
  12. changed = False
  13. # Select flow with a literal condition can be optimized to a direct jump.
  14. if (isinstance(block.flow, cfg_ir.SelectFlow)
  15. and cfg_ir.is_literal_def(block.flow.condition)):
  16. literal = cfg_ir.get_literal_def_value(block.flow.condition)
  17. block.flow = cfg_ir.JumpFlow(
  18. block.flow.if_branch if literal else block.flow.else_branch)
  19. changed = True
  20. # Jumps to blocks which contain no parameters or definitions can be replaced
  21. # by the target block's flow.
  22. if (isinstance(block.flow, cfg_ir.JumpFlow)
  23. and is_empty_block(block.flow.branch.block)
  24. and block.flow.branch.block is not block):
  25. block.flow = block.flow.branch.block.flow
  26. changed = True
  27. # Branches to blocks which contain nothing but a jump can be replaced by branches
  28. # to the jump's target.
  29. for branch in block.flow.branches():
  30. if (is_empty_block(branch.block)
  31. and branch.block is not block
  32. and isinstance(branch.block.flow, cfg_ir.JumpFlow)):
  33. new_branch = branch.block.flow.branch
  34. branch.block = new_branch.block
  35. branch.arguments = new_branch.arguments
  36. changed = True
  37. def optimize_graph_flow(entry_point):
  38. """Optimizes all flow instructions in the graph defined by the given entry point."""
  39. for block in cfg_ir.get_all_blocks(entry_point):
  40. optimize_flow(block)
  41. def merge_blocks(entry_point):
  42. """Merges blocks which have exactly one predecessor with said predecessor, if the
  43. predecessor has a jump flow instruction."""
  44. predecessor_map = cfg_ir.get_all_predecessor_blocks(entry_point)
  45. queue = list(predecessor_map.keys())
  46. def __do_merge(source, target):
  47. for target_param, branch_arg in zip(target.parameters, source.flow.branch.arguments):
  48. source.append_definition(target_param)
  49. target_param.redefine(branch_arg)
  50. for target_def in target.definitions:
  51. source.append_definition(target_def)
  52. source.flow = target.flow
  53. for pred_set in predecessor_map.values():
  54. if target in pred_set:
  55. pred_set.remove(target)
  56. pred_set.add(source)
  57. while len(queue) > 0:
  58. block = queue.pop()
  59. preds = predecessor_map[block]
  60. if len(preds) == 1:
  61. single_pred = next(iter(preds))
  62. if isinstance(single_pred.flow, cfg_ir.JumpFlow):
  63. __do_merge(single_pred, block)
  64. def elide_local_checks(entry_point):
  65. """Tries to elide redundant checks on local variables."""
  66. # The plan here is to replace all check-local-exists defs by literals if
  67. # they are either dominated by an appropriate declare-local or not reachable
  68. # from a declare-local.
  69. local_checks = defaultdict(set)
  70. local_defs = defaultdict(set)
  71. for block in cfg_ir.get_all_blocks(entry_point):
  72. for definition in block.definitions:
  73. if cfg_ir.is_value_def(definition, cfg_ir.CheckLocalExists):
  74. local_checks[cfg_ir.get_def_variable(definition).node_id].add(definition)
  75. elif cfg_ir.is_value_def(definition, cfg_ir.DeclareLocal):
  76. local_defs[cfg_ir.get_def_variable(definition).node_id].add(definition)
  77. dominator_tree = cfg_dominators.get_dominator_tree(entry_point)
  78. reachable_blocks = cfg_ir.get_all_reachable_blocks(entry_point)
  79. for (variable, all_checks) in local_checks.items():
  80. for check in all_checks:
  81. is_reachable = False
  82. for local_def in local_defs[variable]:
  83. if dominator_tree.dominates_instruction(local_def, check):
  84. # Check is dominated by a definition. Replace it by a 'True' literal.
  85. check.redefine(cfg_ir.Literal(True))
  86. is_reachable = True
  87. break
  88. elif check.block in reachable_blocks[local_def.block]:
  89. is_reachable = True
  90. if not is_reachable:
  91. # Check cannot be reached from any definition. Replace it by a 'False' literal.
  92. check.redefine(cfg_ir.Literal(False))
  93. def eliminate_unused_definitions(entry_point):
  94. """Tries to eliminate unused definitions in the control-flow graphb defined by the
  95. given entry point."""
  96. def_dependencies = {}
  97. root_defs = set()
  98. for block in cfg_ir.get_all_blocks(entry_point):
  99. for definition in block.definitions:
  100. def_dependencies[definition] = set(
  101. [dep for dep in definition.get_all_dependencies()
  102. if isinstance(dep, cfg_ir.Definition)])
  103. if definition.has_side_effects():
  104. root_defs.add(definition)
  105. for dep in block.flow.get_all_dependencies():
  106. if isinstance(dep, cfg_ir.Definition):
  107. root_defs.add(dep)
  108. live_defs = set()
  109. def __mark_live(definition):
  110. if definition in live_defs:
  111. return
  112. live_defs.add(definition)
  113. if definition in def_dependencies:
  114. for dep in def_dependencies[definition]:
  115. __mark_live(dep)
  116. for root in root_defs:
  117. __mark_live(root)
  118. dead_defs = set.difference(set(def_dependencies.keys()), live_defs)
  119. for dead_def in dead_defs:
  120. dead_def.block.remove_definition(dead_def)
  121. def get_trivial_phi_value(parameter_def, values):
  122. """Tests if the given parameter definition is an alias for another definition.
  123. If so, then the other definition is returned; otherwise, None."""
  124. result = None
  125. for elem in values:
  126. if elem is not parameter_def:
  127. if result is None:
  128. result = elem
  129. else:
  130. return None
  131. return result
  132. def eliminate_trivial_phis(entry_point):
  133. """Eliminates trivial block parameters, i.e., block parameters which are really
  134. aliases."""
  135. phi_values = defaultdict(set)
  136. all_blocks = list(cfg_ir.get_all_blocks(entry_point))
  137. for block in all_blocks:
  138. for branch in block.flow.branches():
  139. for phi, arg in zip(branch.block.parameters, branch.arguments):
  140. phi_values[phi].add(arg)
  141. replacements = []
  142. for block in all_blocks:
  143. block_parameters = list(block.parameters)
  144. for parameter_def in block_parameters:
  145. trivial_phi_val = get_trivial_phi_value(
  146. parameter_def, phi_values[parameter_def])
  147. if trivial_phi_val is not None:
  148. replacements.append((parameter_def, trivial_phi_val))
  149. erase_parameters(entry_point, set([parameter_def for parameter_def, _ in replacements]))
  150. for parameter_def, trivial_phi_val in replacements:
  151. block = parameter_def.block
  152. parameter_def.redefine(trivial_phi_val)
  153. block.prepend_definition(parameter_def)
  154. def erase_parameters(entry_point, parameters_to_erase):
  155. """Erases all arguments for the given set of parameters, and then takes out the
  156. parameters themselves."""
  157. for block in cfg_ir.get_all_blocks(entry_point):
  158. for branch in block.flow.branches():
  159. new_arg_list = []
  160. for parameter, arg in zip(branch.block.parameters, branch.arguments):
  161. if parameter not in parameters_to_erase:
  162. new_arg_list.append(arg)
  163. branch.arguments = new_arg_list
  164. for parameter_def in parameters_to_erase:
  165. parameter_def.block.remove_parameter(parameter_def)
  166. def apply_cfg_intrinsic(intrinsic_function, original_definition, named_args):
  167. """Applies the given intrinsic to the given sequence of named arguments."""
  168. kwargs = dict(named_args)
  169. kwargs['original_def'] = original_definition
  170. return intrinsic_function(**kwargs)
  171. def try_redefine_as_direct_call(definition, jit, called_globals):
  172. """Tries to redefine the given indirect call definition as a direct call."""
  173. call = cfg_ir.get_def_value(definition)
  174. if not isinstance(call, cfg_ir.IndirectFunctionCall):
  175. return
  176. target = cfg_ir.get_def_value(call.target)
  177. if isinstance(target, cfg_ir.LoadPointer):
  178. loaded_ptr = cfg_ir.get_def_value(target.pointer)
  179. if isinstance(loaded_ptr, cfg_ir.ResolveGlobal):
  180. resolved_var_name = loaded_ptr.variable.name
  181. called_globals.add(loaded_ptr)
  182. # Try to resolve the callee as an intrinsic.
  183. intrinsic = jit.get_cfg_intrinsic(resolved_var_name)
  184. if intrinsic is not None:
  185. apply_cfg_intrinsic(intrinsic, definition, call.argument_list)
  186. else:
  187. # Otherwise, build a thunk.
  188. thunk_name = jit.jit_thunk_global(resolved_var_name)
  189. definition.redefine(
  190. cfg_ir.DirectFunctionCall(
  191. thunk_name, call.argument_list, cfg_ir.JIT_CALLING_CONVENTION))
  192. called_globals.add(loaded_ptr)
  193. elif isinstance(target, cfg_ir.Literal):
  194. node_id = target.literal
  195. thunk_name = jit.jit_thunk_constant(node_id)
  196. definition.redefine(
  197. cfg_ir.DirectFunctionCall(
  198. thunk_name, call.argument_list, cfg_ir.JIT_CALLING_CONVENTION))
  199. def get_checked_global(definition):
  200. """If the definition is a check that tests if a global does not exist, then
  201. the instruction that resolves the global is returned; otherwise None."""
  202. def_value = cfg_ir.get_def_value(definition)
  203. if not isinstance(def_value, cfg_ir.Binary):
  204. return None
  205. if def_value.operator != 'is':
  206. return None
  207. def __get_checked_global_single_dir(lhs, rhs):
  208. if (isinstance(lhs, cfg_ir.ResolveGlobal)
  209. and isinstance(rhs, cfg_ir.Literal)
  210. and rhs.literal is None):
  211. return lhs
  212. else:
  213. return None
  214. bin_lhs = cfg_ir.get_def_value(def_value.lhs)
  215. bin_rhs = cfg_ir.get_def_value(def_value.rhs)
  216. result = __get_checked_global_single_dir(bin_lhs, bin_rhs)
  217. if result is None:
  218. result = __get_checked_global_single_dir(bin_rhs, bin_lhs)
  219. return result
  220. def optimize_calls(entry_point, jit):
  221. """Converts indirect calls to direct calls in the control-flow graph defined by the
  222. given entry point."""
  223. called_globals = set()
  224. global_exists_defs = defaultdict(list)
  225. all_blocks = list(cfg_ir.get_all_blocks(entry_point))
  226. for block in all_blocks:
  227. for definition in block.definitions:
  228. checked_global = get_checked_global(definition)
  229. if checked_global is not None:
  230. global_exists_defs[checked_global].append(definition)
  231. else:
  232. try_redefine_as_direct_call(definition, jit, called_globals)
  233. for resolve_global in called_globals:
  234. for exists_def in global_exists_defs[resolve_global]:
  235. exists_def.redefine(cfg_ir.Literal(False))
  236. def simplify_values(entry_point):
  237. """Simplifies values in the control-flow graph defined by the given entry point."""
  238. for block in cfg_ir.get_all_blocks(entry_point):
  239. for definition in block.definitions:
  240. def_val = cfg_ir.get_def_value(definition)
  241. if isinstance(def_val, cfg_ir.Read):
  242. read_node = cfg_ir.get_def_value(def_val.node)
  243. if isinstance(read_node, cfg_ir.CreateNode):
  244. definition.redefine(read_node.value)
  245. elif isinstance(def_val, cfg_ir.Binary):
  246. lhs = cfg_ir.get_def_value(def_val.lhs)
  247. rhs = cfg_ir.get_def_value(def_val.rhs)
  248. if isinstance(lhs, cfg_ir.Literal) and isinstance(rhs, cfg_ir.Literal):
  249. definition.redefine(
  250. cfg_ir.Literal(
  251. eval('%r %s %r' % (lhs.literal, def_val.operator, rhs.literal))))
  252. def inline_constants(entry_point):
  253. """Replaces reads of constant nodes by the literals they contain."""
  254. for block in cfg_ir.get_all_blocks(entry_point):
  255. for definition in block.definitions:
  256. def_val = cfg_ir.get_def_value(definition)
  257. if isinstance(def_val, cfg_ir.Read):
  258. read_node = cfg_ir.get_def_value(def_val.node)
  259. if isinstance(read_node, cfg_ir.Literal):
  260. val, = yield [("RV", [read_node.literal])]
  261. definition.redefine(cfg_ir.Literal(val))
  262. def optimize(entry_point, jit):
  263. """Optimizes the control-flow graph defined by the given entry point."""
  264. optimize_graph_flow(entry_point)
  265. elide_local_checks(entry_point)
  266. optimize_graph_flow(entry_point)
  267. eliminate_trivial_phis(entry_point)
  268. optimize_calls(entry_point, jit)
  269. yield [("CALL_ARGS", [inline_constants, (entry_point,)])]
  270. simplify_values(entry_point)
  271. eliminate_unused_definitions(entry_point)
  272. optimize_graph_flow(entry_point)
  273. merge_blocks(entry_point)
  274. eliminate_unused_definitions(entry_point)