123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157 |
- """Optimizes and analyzes CFG-IR."""
- from collections import defaultdict
- import modelverse_jit.cfg_ir as cfg_ir
- import modelverse_jit.cfg_dominators as cfg_dominators
- def get_directly_reachable_blocks(block):
- """Gets the set of all blocks that can be reached by taking a single branch from the
- given block."""
- return [branch.block for branch in block.flow.branches()]
- def get_reachable_blocks(entry_point):
- """Constructs the set of all reachable vertices from the given block."""
- # This is a simple O(n^2) algorithm. Maybe a faster algorithm is more appropriate here.
- def __add_block_children(block, results):
- for child in get_directly_reachable_blocks(block):
- if child not in results:
- results.add(child)
- __add_block_children(child, results)
- return results
- return __add_block_children(entry_point, set())
- def get_all_reachable_blocks(entry_point):
- """Constructs the set of all reachable vertices, for every block that is
- reachable from the given entry point."""
- # This is a simple O(n^3) algorithm. Maybe a faster algorithm is more appropriate here.
- results = {}
- all_blocks = get_reachable_blocks(entry_point)
- results[entry_point] = all_blocks
- for block in all_blocks:
- if block not in results:
- results[block] = get_reachable_blocks(block)
- return results
- def is_empty_block(block):
- """Tests if the given block contains no parameters or definitions."""
- return len(block.parameters) == 0 and len(block.definitions) == 0
- def optimize_flow(block):
- """Optimizes the given block's flow instruction."""
- changed = True
- while changed:
- changed = False
- # Select flow with a literal condition can be optimized to a direct jump.
- if (isinstance(block.flow, cfg_ir.SelectFlow)
- and cfg_ir.is_literal_def(block.flow.condition)):
- literal = cfg_ir.get_literal_def_value(block.flow.condition)
- block.flow = cfg_ir.JumpFlow(
- block.flow.if_branch if literal else block.flow.else_branch)
- changed = True
- # Jumps to blocks which contain no parameters or definitions can be replaced
- # by the target block's flow.
- if (isinstance(block.flow, cfg_ir.JumpFlow)
- and is_empty_block(block.flow.branch.block)
- and block.flow.branch.block is not block):
- block.flow = block.flow.branch.block.flow
- changed = True
- # Branches to blocks which contain nothing but a jump can be replaced by branches
- # to the jump's target.
- for branch in block.flow.branches():
- if (is_empty_block(branch.block)
- and branch.block is not block
- and isinstance(branch.block.flow, cfg_ir.JumpFlow)):
- new_branch = branch.block.flow.branch
- branch.block = new_branch.block
- branch.arguments = new_branch.arguments
- changed = True
- def get_all_blocks(entry_point):
- """Gets all basic blocks in the control-flow graph defined by the given entry point."""
- yield entry_point
- for block in get_reachable_blocks(entry_point):
- yield block
- def optimize_graph_flow(entry_point):
- """Optimizes all flow instructions in the graph defined by the given entry point."""
- for block in get_all_blocks(entry_point):
- optimize_flow(block)
- def elide_local_checks(entry_point):
- """Tries to elide redundant checks on local variables."""
- # The plan here is to replace all check-local-exists defs by literals if
- # they are either dominated by an appropriate declare-local or not reachable
- # from a declare-local.
- local_checks = defaultdict(set)
- local_defs = defaultdict(set)
- for block in get_all_blocks(entry_point):
- for definition in block.definitions:
- if cfg_ir.is_value_def(definition, cfg_ir.CheckLocalExists):
- local_checks[cfg_ir.get_def_variable(definition).node_id].add(definition)
- elif cfg_ir.is_value_def(definition, cfg_ir.DeclareLocal):
- local_defs[cfg_ir.get_def_variable(definition).node_id].add(definition)
- dominator_tree = cfg_dominators.get_dominator_tree(entry_point)
- reachable_blocks = get_all_reachable_blocks(entry_point)
- for (variable, all_checks) in local_checks.items():
- for check in all_checks:
- is_reachable = False
- for local_def in local_defs[variable]:
- if dominator_tree.dominates_instruction(local_def, check):
- # Check is dominated by a definition. Replace it by a 'True' literal.
- check.redefine(cfg_ir.Literal(True))
- break
- elif check.block in reachable_blocks[local_def.block]:
- is_reachable = True
- if not is_reachable:
- # Check cannot be reached from any definition. Replace it by a 'False' literal.
- check.redefine(cfg_ir.Literal(False))
- def eliminate_unused_definitions(entry_point):
- """Tries to eliminate unused definitions in the control-flow graphb defined by the
- given entry point."""
- def_dependencies = {}
- root_defs = set()
- for block in get_all_blocks(entry_point):
- for definition in block.definitions:
- def_dependencies[definition] = set(
- [dep for dep in definition.get_all_dependencies()
- if isinstance(dep, cfg_ir.Definition)])
- if definition.has_side_effects():
- root_defs.add(definition)
- for dep in block.flow.get_all_dependencies():
- if isinstance(dep, cfg_ir.Definition):
- root_defs.add(dep)
- live_defs = set()
- def __mark_live(definition):
- if definition in live_defs:
- return
- live_defs.add(definition)
- if definition in def_dependencies:
- for dep in def_dependencies[definition]:
- __mark_live(dep)
- for root in root_defs:
- __mark_live(root)
- dead_defs = set.difference(set(def_dependencies.keys()), live_defs)
- for dead_def in dead_defs:
- dead_def.block.remove_definition(dead_def)
- def optimize(entry_point):
- """Optimizes the control-flow graph defined by the given entry point."""
- optimize_graph_flow(entry_point)
- elide_local_checks(entry_point)
- optimize_graph_flow(entry_point)
- eliminate_unused_definitions(entry_point)
- optimize_graph_flow(entry_point)
|