cfg_optimization.py 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530
  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_jit.cfg_data_structures as cfg_data_structures
  7. import modelverse_jit.tree_ir as tree_ir
  8. import modelverse_kernel.primitives as primitive_functions
  9. def is_empty_block(block):
  10. """Tests if the given block contains no parameters or definitions."""
  11. return len(block.parameters) == 0 and len(block.definitions) == 0
  12. def optimize_flow(block):
  13. """Optimizes the given block's flow instruction."""
  14. changed = True
  15. while changed:
  16. changed = False
  17. # Select flow with a literal condition can be optimized to a direct jump.
  18. if (isinstance(block.flow, cfg_ir.SelectFlow)
  19. and cfg_ir.is_literal_def(block.flow.condition)):
  20. literal = cfg_ir.get_literal_def_value(block.flow.condition)
  21. block.flow = cfg_ir.JumpFlow(
  22. block.flow.if_branch if literal else block.flow.else_branch)
  23. changed = True
  24. # Jumps to blocks which contain no parameters or definitions can be replaced
  25. # by the target block's flow.
  26. if (isinstance(block.flow, cfg_ir.JumpFlow)
  27. and is_empty_block(block.flow.branch.block)
  28. and block.flow.branch.block is not block):
  29. block.flow = block.flow.branch.block.flow
  30. changed = True
  31. # Branches to blocks which contain nothing but a jump can be replaced by branches
  32. # to the jump's target.
  33. for branch in block.flow.branches():
  34. if (is_empty_block(branch.block)
  35. and branch.block is not block
  36. and isinstance(branch.block.flow, cfg_ir.JumpFlow)):
  37. new_branch = branch.block.flow.branch
  38. branch.block = new_branch.block
  39. branch.arguments = new_branch.arguments
  40. changed = True
  41. def optimize_graph_flow(entry_point):
  42. """Optimizes all flow instructions in the graph defined by the given entry point."""
  43. for block in cfg_ir.get_all_blocks(entry_point):
  44. optimize_flow(block)
  45. def merge_blocks(entry_point):
  46. """Merges blocks which have exactly one predecessor with said predecessor, if the
  47. predecessor has a jump flow instruction."""
  48. predecessor_map = cfg_ir.get_all_predecessor_blocks(entry_point)
  49. queue = set(predecessor_map.keys())
  50. queue.add(entry_point)
  51. def __do_merge(source, target):
  52. target_params = list(target.parameters)
  53. branch_args = list(source.flow.branch.arguments)
  54. for target_param, branch_arg in zip(target_params, branch_args):
  55. target.remove_parameter(target_param)
  56. target_param.redefine(branch_arg)
  57. source.append_definition(target_param)
  58. target_defs = list(target.definitions)
  59. for target_def in target_defs:
  60. target.remove_definition(target_def)
  61. source.append_definition(target_def)
  62. source.flow = target.flow
  63. for preds in predecessor_map.values():
  64. if target in preds:
  65. preds[preds.index(target)] = source
  66. # preds.remove(target)
  67. # preds.add(source)
  68. while len(queue) > 0:
  69. block = queue.pop()
  70. if isinstance(block.flow, cfg_ir.JumpFlow):
  71. next_block = block.flow.branch.block
  72. preds = predecessor_map[next_block]
  73. if (len(preds) == 1
  74. and next(iter(preds)) == block
  75. and block != next_block
  76. and next_block != entry_point):
  77. __do_merge(block, next_block)
  78. del predecessor_map[next_block]
  79. queue.add(block)
  80. if next_block in queue:
  81. queue.remove(next_block)
  82. def elide_local_checks(entry_point):
  83. """Tries to elide redundant checks on local variables."""
  84. # The plan here is to replace all check-local-exists defs by literals if
  85. # they are either dominated by an appropriate declare-local or not reachable
  86. # from a declare-local.
  87. local_checks = []
  88. local_defs = defaultdict(list)
  89. for block in cfg_ir.get_all_blocks(entry_point):
  90. for definition in block.definitions:
  91. def_value = definition.value
  92. if isinstance(def_value, cfg_ir.CheckLocalExists):
  93. local_checks.append((def_value.variable.node_id, definition))
  94. elif isinstance(def_value, cfg_ir.DeclareLocal):
  95. local_defs[def_value.variable.node_id].append(definition)
  96. dominator_tree = cfg_dominators.get_dominator_tree(entry_point)
  97. reachable_blocks = cfg_ir.get_all_reachable_blocks(entry_point)
  98. for (variable, check) in local_checks:
  99. is_reachable = False
  100. for local_def in local_defs[variable]:
  101. if dominator_tree.dominates_instruction(local_def, check):
  102. # Check is dominated by a definition. Replace it by a 'True' literal.
  103. check.redefine(cfg_ir.Literal(True))
  104. is_reachable = True
  105. break
  106. elif check.block in reachable_blocks[local_def.block]:
  107. is_reachable = True
  108. if not is_reachable:
  109. # Check cannot be reached from any definition. Replace it by a 'False' literal.
  110. check.redefine(cfg_ir.Literal(False))
  111. def eliminate_unused_definitions(entry_point):
  112. """Tries to eliminate unused definitions in the control-flow graphb defined by the
  113. given entry point."""
  114. def_dependencies = defaultdict(set)
  115. root_defs = set()
  116. # Gather dependencies.
  117. for block in cfg_ir.get_all_blocks(entry_point):
  118. for definition in block.parameters + block.definitions:
  119. all_dependencies = list(definition.get_all_dependencies())
  120. def_dependencies[definition].update(
  121. [dep for dep in all_dependencies
  122. if isinstance(dep, cfg_ir.Definition)])
  123. if len(all_dependencies) > 0 and definition.has_bidirectional_dependencies():
  124. for dep in all_dependencies:
  125. def_dependencies[dep].add(definition)
  126. if definition.has_side_effects():
  127. root_defs.add(definition)
  128. for dep in block.flow.get_dependencies():
  129. if isinstance(dep, cfg_ir.Definition):
  130. root_defs.add(dep)
  131. else:
  132. assert isinstance(dep, cfg_ir.Branch)
  133. for param, arg in zip(dep.block.parameters, dep.arguments):
  134. def_dependencies[param].add(arg)
  135. # Figure out which definitions are live.
  136. live_defs = set()
  137. def __mark_live(definition):
  138. if definition in live_defs:
  139. return
  140. live_defs.add(definition)
  141. if definition in def_dependencies:
  142. for dep in def_dependencies[definition]:
  143. __mark_live(dep)
  144. for root in root_defs:
  145. __mark_live(root)
  146. # Remove all dead definitions.
  147. dead_defs = set.difference(set(def_dependencies.keys()), live_defs)
  148. dead_phis = set()
  149. for dead_def in dead_defs:
  150. if isinstance(dead_def.value, cfg_ir.BlockParameter):
  151. dead_phis.add(dead_def)
  152. else:
  153. dead_def.block.remove_definition(dead_def)
  154. erase_parameters(entry_point, dead_phis)
  155. def eliminate_trivial_phis(entry_point):
  156. """Eliminates trivial block parameters, i.e., block parameters which are really
  157. aliases."""
  158. phi_values = defaultdict(set)
  159. all_blocks = list(cfg_ir.get_all_blocks(entry_point))
  160. for block in all_blocks:
  161. for branch in block.flow.branches():
  162. for phi, arg in zip(branch.block.parameters, branch.arguments):
  163. phi_values[phi].add(arg)
  164. replacements = []
  165. for block in all_blocks:
  166. block_parameters = list(block.parameters)
  167. for parameter_def in block_parameters:
  168. trivial_phi_val = cfg_ir.get_trivial_phi_value(
  169. parameter_def, phi_values[parameter_def])
  170. if trivial_phi_val is not None:
  171. replacements.append((parameter_def, trivial_phi_val))
  172. erase_parameters(entry_point, set([parameter_def for parameter_def, _ in replacements]))
  173. for parameter_def, trivial_phi_val in replacements:
  174. block = parameter_def.block
  175. parameter_def.redefine(trivial_phi_val)
  176. block.prepend_definition(parameter_def)
  177. def erase_parameters(entry_point, parameters_to_erase):
  178. """Erases all arguments for the given set of parameters, and then takes out the
  179. parameters themselves."""
  180. for block in cfg_ir.get_all_blocks(entry_point):
  181. for branch in block.flow.branches():
  182. new_arg_list = []
  183. for parameter, arg in zip(branch.block.parameters, branch.arguments):
  184. if parameter not in parameters_to_erase:
  185. new_arg_list.append(arg)
  186. branch.arguments = new_arg_list
  187. for parameter_def in parameters_to_erase:
  188. parameter_def.block.remove_parameter(parameter_def)
  189. def apply_cfg_intrinsic(intrinsic_function, original_definition, named_args):
  190. """Applies the given intrinsic to the given sequence of named arguments."""
  191. kwargs = dict(named_args)
  192. kwargs['original_def'] = original_definition
  193. return intrinsic_function(**kwargs)
  194. def try_redefine_as_direct_call(definition, jit, called_globals):
  195. """Tries to redefine the given indirect call definition as a direct call."""
  196. call = cfg_ir.get_def_value(definition)
  197. if not isinstance(call, cfg_ir.IndirectFunctionCall):
  198. return
  199. target = cfg_ir.get_def_value(call.target)
  200. if isinstance(target, cfg_ir.LoadPointer):
  201. loaded_ptr = cfg_ir.get_def_value(target.pointer)
  202. if isinstance(loaded_ptr, cfg_ir.ResolveGlobal):
  203. resolved_var_name = loaded_ptr.variable.name
  204. called_globals.add(loaded_ptr)
  205. # Try to resolve the callee as an intrinsic.
  206. intrinsic = jit.get_cfg_intrinsic(resolved_var_name)
  207. if intrinsic is not None:
  208. apply_cfg_intrinsic(intrinsic, definition, call.argument_list)
  209. else:
  210. # Otherwise, build a thunk.
  211. thunk_name = jit.jit_thunk_global(resolved_var_name)
  212. calling_convention = (
  213. cfg_ir.JIT_NO_GC_CALLING_CONVENTION
  214. if jit.get_intrinsic(thunk_name) is not None
  215. else cfg_ir.JIT_CALLING_CONVENTION)
  216. definition.redefine(
  217. cfg_ir.DirectFunctionCall(
  218. thunk_name, call.argument_list, calling_convention))
  219. called_globals.add(loaded_ptr)
  220. elif isinstance(target, cfg_ir.Literal):
  221. node_id = target.literal
  222. thunk_name = jit.jit_thunk_constant_function(node_id)
  223. definition.redefine(
  224. cfg_ir.DirectFunctionCall(
  225. thunk_name, call.argument_list, cfg_ir.JIT_CALLING_CONVENTION))
  226. def get_checked_global(definition):
  227. """If the definition is a check that tests if a global does not exist, then
  228. the instruction that resolves the global is returned; otherwise None."""
  229. def_value = cfg_ir.get_def_value(definition)
  230. if not isinstance(def_value, cfg_ir.Binary):
  231. return None
  232. if def_value.operator != 'is':
  233. return None
  234. def __get_checked_global_single_dir(lhs, rhs):
  235. if (isinstance(lhs, cfg_ir.ResolveGlobal)
  236. and isinstance(rhs, cfg_ir.Literal)
  237. and rhs.literal is None):
  238. return lhs
  239. else:
  240. return None
  241. bin_lhs = cfg_ir.get_def_value(def_value.lhs)
  242. bin_rhs = cfg_ir.get_def_value(def_value.rhs)
  243. result = __get_checked_global_single_dir(bin_lhs, bin_rhs)
  244. if result is None:
  245. result = __get_checked_global_single_dir(bin_rhs, bin_lhs)
  246. return result
  247. def optimize_calls(entry_point, jit):
  248. """Converts indirect calls to direct calls in the control-flow graph defined by the
  249. given entry point."""
  250. called_globals = set()
  251. global_exists_defs = defaultdict(list)
  252. all_blocks = list(cfg_ir.get_all_blocks(entry_point))
  253. for block in all_blocks:
  254. for definition in block.definitions:
  255. checked_global = get_checked_global(definition)
  256. if checked_global is not None:
  257. global_exists_defs[checked_global].append(definition)
  258. else:
  259. try_redefine_as_direct_call(definition, jit, called_globals)
  260. for resolve_global in called_globals:
  261. for exists_def in global_exists_defs[resolve_global]:
  262. exists_def.redefine(cfg_ir.Literal(False))
  263. def simplify_values(entry_point):
  264. """Simplifies values in the control-flow graph defined by the given entry point."""
  265. for block in cfg_ir.get_all_blocks(entry_point):
  266. for definition in block.definitions:
  267. def_val = cfg_ir.get_def_value(definition)
  268. if isinstance(def_val, cfg_ir.Read):
  269. read_node = cfg_ir.get_def_value(def_val.node)
  270. if isinstance(read_node, cfg_ir.CreateNode):
  271. definition.redefine(read_node.value)
  272. elif isinstance(def_val, cfg_ir.Binary):
  273. lhs = cfg_ir.get_def_value(def_val.lhs)
  274. rhs = cfg_ir.get_def_value(def_val.rhs)
  275. if isinstance(lhs, cfg_ir.Literal) and isinstance(rhs, cfg_ir.Literal):
  276. definition.redefine(
  277. cfg_ir.Literal(
  278. eval('%r %s %r' % (lhs.literal, def_val.operator, rhs.literal))))
  279. elif isinstance(def_val, cfg_ir.Unary):
  280. operand = cfg_ir.get_def_value(def_val.operand)
  281. if isinstance(operand, cfg_ir.Literal):
  282. definition.redefine(
  283. cfg_ir.Literal(
  284. eval('%s %r' % (def_val.operator, operand.literal))))
  285. def inline_constants(entry_point):
  286. """Replaces reads of constant nodes by the literals they contain."""
  287. for block in cfg_ir.get_all_blocks(entry_point):
  288. for definition in block.definitions:
  289. def_val = cfg_ir.get_def_value(definition)
  290. if isinstance(def_val, cfg_ir.Read):
  291. read_node = cfg_ir.get_def_value(def_val.node)
  292. if isinstance(read_node, cfg_ir.Literal):
  293. val, = yield [("RV", [read_node.literal])]
  294. definition.redefine(cfg_ir.Literal(val))
  295. def expand_indirect_definitions(entry_point):
  296. """Replaces indirect definitions by the values referred to by those definitions."""
  297. def __expand_indirect_defs(value):
  298. dependencies = value.get_dependencies()
  299. if len(dependencies) == 0:
  300. return value
  301. else:
  302. new_dependencies = []
  303. for dep in dependencies:
  304. new_dep = dep
  305. if isinstance(new_dep, cfg_ir.Definition):
  306. while isinstance(new_dep.value, cfg_ir.Definition):
  307. new_dep = new_dep.value
  308. else:
  309. new_dep = __expand_indirect_defs(new_dep)
  310. new_dependencies.append(new_dep)
  311. return value.create(new_dependencies)
  312. for block in cfg_ir.get_all_blocks(entry_point):
  313. block_definitions = list(block.definitions)
  314. for definition in block_definitions:
  315. if isinstance(definition.value, cfg_ir.Definition):
  316. block.remove_definition(definition)
  317. else:
  318. definition.redefine(
  319. __expand_indirect_defs(definition.value))
  320. block.flow = __expand_indirect_defs(block.flow)
  321. def optimize_reads(entry_point):
  322. """Tries to replace repeated reads by a single read."""
  323. cfg_ir.match_and_rewrite(
  324. entry_point,
  325. lambda _: True,
  326. lambda use_def, _: cfg_ir.is_value_def(use_def, cfg_ir.Read),
  327. lambda def_def:
  328. def_def.redefine(
  329. cfg_ir.Read(def_def.insert_before(def_def.value))),
  330. lambda use_def, def_def: use_def.redefine(def_def))
  331. def protect_from_gc(entry_point):
  332. """Protects locals in the control-flow graph defined by the given
  333. entry point from the GC."""
  334. root_node = entry_point.prepend_definition(cfg_ir.AllocateRootNode())
  335. def protect_def_from_gc(definition):
  336. """Protects the given definition from the GC."""
  337. definition.insert_after(cfg_ir.create_gc_protect(definition, root_node))
  338. def maybe_protect_def_from_gc(definition):
  339. """Protects the given definition from the GC, if its result is not None."""
  340. definition.insert_after(cfg_ir.create_conditional_gc_protect(definition, root_node))
  341. for block in cfg_ir.get_all_blocks(entry_point):
  342. for definition in block.definitions:
  343. def_value = cfg_ir.get_def_value(definition)
  344. if isinstance(def_value, cfg_ir.CreateNode):
  345. protect_def_from_gc(definition)
  346. elif (isinstance(def_value, cfg_ir.IndirectFunctionCall)
  347. or (isinstance(def_value, cfg_ir.DirectFunctionCall)
  348. and (def_value.calling_convention == cfg_ir.JIT_CALLING_CONVENTION
  349. or def_value.calling_convention == cfg_ir.JIT_NO_GC_CALLING_CONVENTION
  350. or def_value.calling_convention == cfg_ir.MACRO_IO_CALLING_CONVENTION)
  351. and def_value.has_value())):
  352. maybe_protect_def_from_gc(definition)
  353. if isinstance(block.flow, (cfg_ir.ReturnFlow, cfg_ir.ThrowFlow)):
  354. block.append_definition(cfg_ir.DeallocateRootNode(root_node))
  355. def elide_gc_protects(entry_point):
  356. """Tries to elide GC protection values."""
  357. # We don't need to protect a value from the GC if it is used for the
  358. # last time _before_ the GC has an opportunity to kick in. To simplify
  359. # things, we'll do a quick block-based analysis.
  360. def __may_cause_gc(definition):
  361. def_value = cfg_ir.get_def_value(definition)
  362. if isinstance(def_value, cfg_ir.IndirectFunctionCall):
  363. return True
  364. elif (isinstance(def_value, cfg_ir.DirectFunctionCall)
  365. and (def_value.calling_convention == cfg_ir.JIT_CALLING_CONVENTION
  366. or def_value.calling_convention == cfg_ir.MACRO_IO_CALLING_CONVENTION)):
  367. return True
  368. else:
  369. return False
  370. def __get_protected_def(def_or_value):
  371. value = cfg_ir.get_def_value(def_or_value)
  372. if cfg_ir.is_call(
  373. value, target_name=cfg_ir.GC_PROTECT_MACRO_NAME,
  374. calling_convention=cfg_ir.MACRO_POSITIONAL_CALLING_CONVENTION):
  375. _, protected_def = value.argument_list[0]
  376. return protected_def
  377. elif cfg_ir.is_call(
  378. value, target_name=cfg_ir.MAYBE_GC_PROTECT_MACRO_NAME,
  379. calling_convention=cfg_ir.MACRO_POSITIONAL_CALLING_CONVENTION):
  380. _, protected_def = value.argument_list[1]
  381. return protected_def
  382. else:
  383. return None
  384. def_blocks = {}
  385. def __register_def_or_use(definition, block):
  386. if definition in def_blocks and def_blocks[definition] != block:
  387. # Definition seems to be used across basic blocks.
  388. ineligible_defs.add(definition)
  389. def_blocks[definition] = block
  390. ineligible_defs = set()
  391. def_protections = defaultdict(list)
  392. for block in cfg_ir.get_all_blocks(entry_point):
  393. no_gc_defs = set()
  394. block_defs = set()
  395. first_gc = {}
  396. last_def_uses = {}
  397. for i, definition in enumerate(block.definitions):
  398. if isinstance(definition.value, cfg_ir.Definition):
  399. # Handling definitions of definitions is complicated and they should already have
  400. # been expanded at this point. Just mark them as ineligible.
  401. ineligible_defs.add(definition)
  402. ineligible_defs.add(definition.value)
  403. continue
  404. protected_def = __get_protected_def(definition)
  405. if protected_def is not None:
  406. # We just ran into a gc_protect/maybe_gc_protect.
  407. def_protections[protected_def].append(definition)
  408. continue
  409. block_defs.add(definition)
  410. __register_def_or_use(definition, block)
  411. for dependency in definition.get_all_dependencies():
  412. __register_def_or_use(dependency, block)
  413. last_def_uses[dependency] = i
  414. if __may_cause_gc(definition):
  415. for gc_def in no_gc_defs:
  416. first_gc[gc_def] = i
  417. no_gc_defs = set()
  418. no_gc_defs.add(definition)
  419. # Mark all branch arguments as ineligible.
  420. for branch in block.flow.branches():
  421. ineligible_defs.update(branch.arguments)
  422. for dependency in block.flow.get_dependencies():
  423. last_def_uses[dependency] = None
  424. for definition in block_defs:
  425. if definition in ineligible_defs:
  426. # Definition was already ineligible.
  427. continue
  428. # Mark `definition` as ineligible if there is a GC definition in the range of
  429. # definitions (definition, last_def_uses[definition]].
  430. if definition in first_gc:
  431. if definition in last_def_uses:
  432. last_use = last_def_uses[definition]
  433. if last_use is None or first_gc[definition] <= last_use:
  434. ineligible_defs.add(definition)
  435. # Elide all GC protections for definitions which are not in the `ineligible_defs` set.
  436. for protected, protections in def_protections.items():
  437. if protected not in ineligible_defs:
  438. for protect_def in protections:
  439. protect_def.redefine(cfg_ir.Literal(None))
  440. def optimize(entry_point, jit):
  441. """Optimizes the control-flow graph defined by the given entry point.
  442. A potentially altered entry point is returned."""
  443. optimize_graph_flow(entry_point)
  444. elide_local_checks(entry_point)
  445. optimize_graph_flow(entry_point)
  446. eliminate_trivial_phis(entry_point)
  447. entry_point = cfg_ssa_construction.construct_ssa_form(entry_point)
  448. if jit.direct_calls_allowed:
  449. optimize_calls(entry_point, jit)
  450. cfg_data_structures.optimize_data_structures(entry_point)
  451. yield [("CALL_ARGS", [inline_constants, (entry_point,)])]
  452. optimize_reads(entry_point)
  453. simplify_values(entry_point)
  454. eliminate_unused_definitions(entry_point)
  455. optimize_graph_flow(entry_point)
  456. expand_indirect_definitions(entry_point)
  457. eliminate_unused_definitions(entry_point)
  458. merge_blocks(entry_point)
  459. protect_from_gc(entry_point)
  460. elide_gc_protects(entry_point)
  461. eliminate_unused_definitions(entry_point)
  462. raise primitive_functions.PrimitiveFinished(entry_point)