""" Utility functions to work with dominators in a :py:class:`CFG `. Do not hesitate to look at the source of the functions to get a better understanding of the algorithms. """ from typing import Dict, Set from graphviz import Digraph from Lib.CFG import Block, CFG def computeDom(cfg: CFG) -> Dict[Block, Set[Block]]: """ `computeDom(cfg)` computes the table associating blocks to their dominators in `cfg`. It works by solving the equation system. This is an helper function called during SSA entry. """ all_blocks: Set[Block] = set(cfg.get_blocks()) dominators: Dict[Block, Set[Block]] = dict() for b in all_blocks: if b.get_in(): # If b has some predecessor dominators[b] = all_blocks else: # If b has no predecessors dominators[b] = {b} new_dominators: Dict[Block, Set[Block]] = dict() while True: for b in all_blocks: if b.get_in(): dom_preds = [dominators[b2] for b2 in b.get_in()] new_dominators[b] = {b}.union(set.intersection(*dom_preds)) else: new_dominators[b] = {b} if dominators == new_dominators: break else: dominators = new_dominators new_dominators = dict() return dominators def printDT(filename: str, graph: Dict[Block, Set[Block]]) -> None: # pragma: no cover """Display a graphical rendering of the given domination tree.""" dot = Digraph() for k in graph: dot.node(str(k.get_label())) for k in graph: for v in graph[k]: dot.edge(str(k.get_label()), str(v.get_label())) dot.render(filename, view=True) def computeDT(cfg: CFG, dominators: Dict[Block, Set[Block]], dom_graphs: bool, basename: str) -> Dict[Block, Set[Block]]: """ `computeDT(cfg, dominators)` computes the domination tree of `cfg` using the previously computed `dominators`. It returns `DT`, a dictionary which associates a block with its children in the dominator tree. This is an helper function called during SSA entry. """ # First, compute the immediate dominators idominators: Dict[Block, Block] = {} for b, doms in dominators.items(): # The immediate dominator of b is the unique vertex n ≠ b # which dominates b and is dominated by all vertices in Dom(b) − b. strict_doms = doms - {b} idoms = set() for n in strict_doms: if strict_doms.issubset(dominators[n]): idoms.add(n) if idoms: assert (len(idoms) == 1) idominators[b] = idoms.pop() # Then, simply inverse the relation to obtain the domination tree DT = {b: set() for b in cfg.get_blocks()} for i, idominator in idominators.items(): DT[idominator].add(i) # Print the domination tree if asked if dom_graphs: s = "{}.{}.ssa.DT.dot".format(basename, cfg.fdata.get_name()) print("SSA - domination tree graph:", s) printDT(s, DT) return DT def _computeDF_at_block( cfg: CFG, dominators: Dict[Block, Set[Block]], DT: Dict[Block, Set[Block]], b: Block, DF: Dict[Block, Set[Block]]) -> None: """ `_computeDF_at_block(...)` computes the dominance frontier at the given block, by updating `DF`. This is an helper function called during SSA entry. """ S: Set[Block] = {succ for succ in cfg.out_blocks(b) if succ not in DT[b]} for b_succ in DT[b]: _computeDF_at_block(cfg, dominators, DT, b_succ, DF) for b_frontier in DF[b_succ]: if b not in (dominators[b_frontier] - {b_frontier}): S.add(b_frontier) DF[b] = S def computeDF(cfg: CFG, dominators: Dict[Block, Set[Block]], DT: Dict[Block, Set[Block]], dom_graphs: bool, basename: str ) -> Dict[Block, Set[Block]]: """ `computeDF(...)` computes the dominance frontier of a CFG. It returns `DF` which associates a block to its frontier. This is an helper function called during SSA entry. """ DF: Dict[Block, Set[Block]] = dict() for b_entry in cfg.get_entries(): _computeDF_at_block(cfg, dominators, DT, b_entry, DF) # Print the domination frontier on the CFG if asked if dom_graphs: s = "{}.{}.ssa.DF.dot".format(basename, cfg.fdata.get_name()) print("SSA - dominance frontier graph:", s) cfg.print_dot(s, DF, True) return DF