cfg_optimization.py 15 KB

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