diff --git a/MiniC/Lib/Dominators.py b/MiniC/Lib/Dominators.py new file mode 100644 index 0000000..71fffe0 --- /dev/null +++ b/MiniC/Lib/Dominators.py @@ -0,0 +1,128 @@ +""" +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 diff --git a/MiniC/Lib/Graphes.py b/MiniC/Lib/Graphes.py new file mode 100644 index 0000000..ed8241d --- /dev/null +++ b/MiniC/Lib/Graphes.py @@ -0,0 +1,306 @@ +""" Python Classes for Oriented and Non Oriented Graphs +""" + +from graphviz import Digraph # for dot output +from typing import List, Dict, Set, Tuple, Any + + +class GraphError(Exception): + """Exception raised for self loops. + """ + + message: str + + def __init__(self, message: str): + self.message = message + + +class GeneralGraph(object): + """ + General class regrouping similarities + between directed and non oriented graphs. + The only differences between the two are: + + - how to compute the set of edges + - how to add an edge + - how to print the graph + - how to delete a vertex + - how to delete an edge + - we only color undirected graphs + """ + + graph_dict: Dict[Any, Set] + + def __init__(self, graph_dict=None): + """ + Initializes a graph object. + If no dictionary or None is given, + an empty dictionary will be used. + """ + if graph_dict is None: + graph_dict = {} + self.graph_dict = graph_dict + + def vertices(self) -> List[Any]: + """Return the vertices of a graph.""" + return list(self.graph_dict.keys()) + + def add_vertex(self, vertex: Any) -> None: + """ + If the vertex "vertex" is not in + self.graph_dict, a key "vertex" with an empty + list as a value is added to the dictionary. + Otherwise nothing has to be done. + """ + if vertex not in self.graph_dict: + self.graph_dict[vertex] = set() + + def edges(self) -> List[Set]: + """Return the edges of the graph.""" + return [] + + def __str__(self): + res = "vertices: " + for k in self.graph_dict: + res += str(k) + " " + res += "\nedges: " + for edge in self.edges(): + res += str(edge) + " " + return res + + def dfs_traversal(self, root: Any) -> List[Any]: + """ + Compute a depth first search of the graph, + from the vertex root. + """ + seen: List[Any] = [] + todo: List[Any] = [root] + while len(todo) > 0: # while todo ... + current = todo.pop() + seen.append(current) + for neighbour in self.graph_dict[current]: + if neighbour not in seen: + todo.append(neighbour) + return seen + + def is_reachable_from(self, v1: Any, v2: Any) -> bool: + """True if there is a path from v1 to v2.""" + return v2 in self.dfs_traversal(v1) + + def connected_components(self) -> List[List[Any]]: + """ + Compute the list of all connected components of the graph, + each component being a list of vetices. + """ + components: List[List[Any]] = [] + done: List[Any] = [] + for v in self.vertices(): + if v not in done: + v_comp = self.dfs_traversal(v) + components.append(v_comp) + done.extend(v_comp) + return components + + def bfs_traversal(self, root: Any) -> List[Any]: + """ + Compute a breadth first search of the graph, + from the vertex root. + """ + seen: List[Any] = [] + todo: List[Any] = [root] + while len(todo) > 0: # while todo ... + current = todo.pop(0) # list.pop(0): for dequeuing (on the left...) ! + seen.append(current) + for neighbour in self.graph_dict[current]: + if neighbour not in seen: + todo.append(neighbour) + return seen + + +class Graph(GeneralGraph): + """Class for non oriented graphs.""" + + def edges(self) -> List[Set]: + """ + A static method generating the set of edges + (they appear twice in the dictionnary). + Return a list of sets. + """ + edges = [] + for vertex in self.graph_dict: + for neighbour in self.graph_dict[vertex]: + if {neighbour, vertex} not in edges: + edges.append({vertex, neighbour}) + return edges + + def add_edge(self, edge: Tuple[Any, Any]) -> None: + """ + Add an edge in the graph. + edge should be a pair and not (c,c) + (we call g.add_edge((v1,v2))) + """ + (vertex1, vertex2) = edge + if vertex1 == vertex2: + raise GraphError("Cannot add a self loop on vertex {} in an unoriented graph.".format( + str(vertex1))) + if vertex1 in self.graph_dict: + self.graph_dict[vertex1].add(vertex2) + else: + self.graph_dict[vertex1] = {vertex2} + if vertex2 in self.graph_dict: + self.graph_dict[vertex2].add(vertex1) + else: + self.graph_dict[vertex2] = {vertex1} + + def print_dot(self, name: str, colors={}) -> None: + """Print the graph.""" + color_names = ['red', 'blue', 'green', 'yellow', 'cyan', 'magenta'] + \ + [f"grey{i}" for i in range(0, 100, 10)] + color_shapes = ['ellipse', 'polygon', 'box', 'circle', 'egg', 'pentagon', 'hexagon'] + dot = Digraph(comment='Conflict Graph') + for k in self.graph_dict: + shape = None + if not colors: + color = "red" # Graph not colored: red for everyone + elif k not in colors: + color = "grey" # Node not colored: grey + else: + n = colors[k] + if n < len(color_names): + color = color_names[colors[k]] + else: + color = "black" # Too many colors anyway, it won't be readable. + shape = color_shapes[n % len(color_shapes)] + dot.node(str(k), color=color, shape=shape) + for (v1, v2) in self.edges(): + dot.edge(str(v1), str(v2), dir="none") + # print(dot.source) + dot.render(name, view=True) # print in pdf + + def delete_vertex(self, vertex: Any) -> None: + """Delete a vertex and all the adjacent edges.""" + gdict = self.graph_dict + for neighbour in gdict[vertex]: + gdict[neighbour].remove(vertex) + del gdict[vertex] + + def delete_edge(self, edge: Tuple[Any, Any]): + """Delete an edge.""" + (v1, v2) = edge + self.graph_dict[v1].remove(v2) + self.graph_dict[v2].remove(v1) + + def color(self) -> Dict[Any, int]: + """ + Color the graph with an unlimited number of colors. + Return a dict vertex -> color, where color is an integer (0, 1, ...). + """ + coloring, _, _ = self.color_with_k_colors() + return coloring + + # see algo of the course + def color_with_k_colors(self, K=None, avoidingnodes=()) -> Tuple[Dict[Any, int], bool, List]: + """ + Color with <= K colors (if K is unspecified, use unlimited colors). + + Return 3 values: + + - a dict vertex -> color + - a Boolean, True if the coloring succeeded + - the set of nodes actually colored + + Do not color vertices belonging to avoidingnodes. + + Continue even if the algo fails. + """ + if K is None: + K = len(self.graph_dict) + todo_vertices = [] + is_total = True + gcopy = Graph(self.graph_dict.copy()) + # suppress nodes that are not to be considered. + for node in avoidingnodes: + gcopy.delete_vertex(node) + # append nodes in the list according to their degree and node number: + while gcopy.graph_dict: + todo = list(gcopy.graph_dict) + todo.sort(key=lambda v: (len(gcopy.graph_dict[v]), str(v))) + lower = todo[0] + todo_vertices.append(lower) + gcopy.delete_vertex(lower) + # Now reverse the list: first elements are those with higher degree + # print(todo_vertices) + todo_vertices.reverse() # in place reversal + # print(todo_vertices) + coloring = {} + colored_nodes = [] + # gdict will be the coloring map to return + gdict = self.graph_dict + for v in todo_vertices: + seen_neighbours = [x for x in gdict[v] if x in coloring] + choose_among = [i for i in range(K) if not ( + i in [coloring[v1] for v1 in seen_neighbours])] + if choose_among: + # if the node can be colored, I choose the minimal color. + color = min(choose_among) + coloring[v] = color + colored_nodes.append(v) + else: + # if I cannot color some node, the coloring is not Total + # but I continue + is_total = False + return (coloring, is_total, colored_nodes) + + +class DiGraph(GeneralGraph): + """Class for directed graphs.""" + + def neighbourhoods(self) -> List[Tuple[Any, Set]]: + """Return all neighbourhoods in the graph.""" + return list(self.graph_dict.items()) + + def edges(self) -> List[Tuple[Any, Any]]: + """ A static method generating the set of edges""" + edges = [] + for vertex in self.graph_dict: + for neighbour in self.graph_dict[vertex]: + edges.append((vertex, neighbour)) + return edges + + def add_edge(self, edge: Tuple[Any, Any]) -> None: + """ + Add an edge in the graph. + edge should be a pair and not (c,c) + (we call g.add_edge((v1,v2))) + """ + (vertex1, vertex2) = edge + if vertex1 in self.graph_dict: + self.graph_dict[vertex1].add(vertex2) + else: + self.graph_dict[vertex1] = {vertex2} + if vertex2 not in self.graph_dict: + self.graph_dict[vertex2] = set() + + def print_dot(self, name: str) -> None: + """Print the graph.""" + dot = Digraph(comment='Conflict Graph') + for k in self.graph_dict: + shape = None + color = "grey" + dot.node(str(k), color=color, shape=shape) + for (v1, v2) in self.edges(): + dot.edge(str(v1), str(v2), dir="none") + # print(dot.source) + dot.render(name, view=True) # print in pdf + + def delete_vertex(self, vertex: Any) -> None: + """Delete a vertex and all the adjacent edges.""" + for node, neighbours in self.graph_dict.items(): + if vertex in neighbours: + neighbours.remove(vertex) + del self.graph_dict[vertex] + + def delete_edge(self, edge: Tuple[Any, Any]) -> None: + """Delete an edge.""" + (v1, v2) = edge + self.graph_dict[v1].remove(v2) diff --git a/MiniC/Lib/PhiNode.py b/MiniC/Lib/PhiNode.py new file mode 100644 index 0000000..ae807ac --- /dev/null +++ b/MiniC/Lib/PhiNode.py @@ -0,0 +1,58 @@ +""" +Classes for φ nodes in a RiscV CFG :py:class:`CFG ` under SSA Form: +:py:class:`PhiNode` for a statement of the form temp_x = φ(temp_0, ..., temp_n). +These particular kinds of statements are expected to be in the field +b._phis for a :py:class:`Block ` b. +""" + +from dataclasses import dataclass +from typing import Dict + +from Lib.Operands import Operand, Temporary, DataLocation, Renamer +from Lib.Statement import Statement, Label + + +@dataclass +class PhiNode(Statement): + """ + A φ node is a renaming in the CFG, of the form temp_x = φ(temp_0, ..., temp_n). + The field var contains the variable temp_x. + The field srcs relies for each precedent block in the CFG, identified with its label, + the variable temp_i of the φ node. + """ + var: DataLocation + srcs: Dict[Label, Operand] + + def defined(self): + """Return the variable defined by the φ node.""" + return [self.var] + + def used(self) -> Dict[Label, Operand]: + """ + Return the dictionnary associating for each previous block the corresponding variable. + """ + return self.srcs + + def rename(self, renamer: Renamer) -> None: + """Rename the variable defined by the φ node with a fresh name.""" + if isinstance(self.var, Temporary): + self.var = renamer.fresh(self.var) + + def rename_from(self, renamer: Renamer, label: Label) -> None: + """Rename the variable associated to the block identified by `label`.""" + if label in self.srcs: + t = self.srcs[label] + if isinstance(t, Temporary): + if renamer.defined(t): + self.srcs[label] = renamer.replace(t) + else: + del self.srcs[label] + + def __str__(self): + return "{} = φ({})".format(self.var, self.srcs) + + def __hash__(self): + return hash((self.var, *self.srcs.items())) + + def printIns(self, stream): + print(' # ' + str(self), file=stream) diff --git a/MiniC/MiniCC.py b/MiniC/MiniCC.py index d82787c..6a98ad7 100644 --- a/MiniC/MiniCC.py +++ b/MiniC/MiniCC.py @@ -5,8 +5,9 @@ Usage: python3 MiniCC.py --mode python3 MiniCC.py --help """ -import traceback from typing import cast +from enum import Enum + from MiniCLexer import MiniCLexer from MiniCParser import MiniCParser from TP03.MiniCTypingVisitor import MiniCTypingVisitor, MiniCTypeError @@ -14,12 +15,11 @@ from TP03.MiniCInterpretVisitor import MiniCInterpretVisitor from Lib.Errors import (MiniCUnsupportedError, MiniCInternalError, MiniCRuntimeError, AllocationError) -from enum import Enum -import argparse - from antlr4 import FileStream, CommonTokenStream from antlr4.error.ErrorListener import ErrorListener +from argparse import ArgumentParser +from traceback import print_exc import os import sys @@ -52,7 +52,7 @@ def valid_modes(): return modes try: - import TP05.SSA # type: ignore[import] + import TP05.EnterSSA # type: ignore[import] modes.append('codegen-ssa') except ImportError: return modes @@ -84,7 +84,7 @@ class CountErrorListener(ErrorListener): def main(inputname, reg_alloc, mode, typecheck=True, stdout=False, output_name=None, debug=False, - debug_graphs=False, ssa_graphs=False): + debug_graphs=False, ssa_graphs=False, dom_graphs=False): (basename, rest) = os.path.splitext(inputname) if mode.is_codegen(): if stdout: @@ -144,22 +144,19 @@ def main(inputname, reg_alloc, mode, code = function else: from TP04.BuildCFG import build_cfg # type: ignore[import] - from Lib.CFG import CFG # type: ignore[import] code = build_cfg(function) - assert (isinstance(code, CFG)) if debug_graphs: s = "{}.{}.dot".format(basename, code.fdata.get_name()) print("CFG:", s) code.print_dot(s, view=True) if mode.value >= Mode.SSA.value: - from TP05.SSA import enter_ssa # type: ignore[import] + from TP05.EnterSSA import enter_ssa # type: ignore[import] from Lib.CFG import CFG # type: ignore[import] - - DF = enter_ssa(cast(CFG, code), basename, debug, ssa_graphs) + enter_ssa(cast(CFG, code), dom_graphs, basename) if ssa_graphs: - s = "{}.{}.ssa.dot".format(basename, code.fdata.get_name()) + s = "{}.{}.enterssa.dot".format(basename, code.fdata.get_name()) print("SSA:", s) - code.print_dot(s, DF, True) + code.print_dot(s, view=True) if mode == Mode.OPTIM: from TPoptim.OptimSSA import OptimSSA # type: ignore[import] OptimSSA(cast(CFG, code), debug=debug) @@ -207,8 +204,8 @@ liveness file not found for {}.".format(form)) allocator.prepare() if mode.value >= Mode.SSA.value: from Lib.CFG import CFG # type: ignore[import] - from TP05.SSA import exit_ssa # type: ignore[import] - exit_ssa(cast(CFG, code)) + from TP05.ExitSSA import exit_ssa # type: ignore[import] + exit_ssa(cast(CFG, code), reg_alloc == 'smart') comment += " with SSA" if allocator: allocator.rewriteCode(code) @@ -233,7 +230,7 @@ if __name__ == '__main__': modes = valid_modes() - parser = argparse.ArgumentParser(description='Generate code for .c file') + parser = ArgumentParser(description='CAP/MIF08 MiniCC compiler') parser.add_argument('filename', type=str, help='Source file.') @@ -265,7 +262,10 @@ if __name__ == '__main__': if "codegen-ssa" in modes: parser.add_argument('--ssa-graphs', action='store_true', default=False, - help='Display SSA graphs (DT, DF).') + help='Display the CFG at SSA entry and exit.') + parser.add_argument('--dom-graphs', action='store_true', + default=False, + help='Display dominance-related graphs (DT, DF).') args = parser.parse_args() reg_alloc = args.reg_alloc if "codegen-linear" in modes else None @@ -273,6 +273,7 @@ if __name__ == '__main__': outfile = args.output if "codegen-linear" in modes else None graphs = args.graphs if "codegen-cfg" in modes else False ssa_graphs = args.ssa_graphs if "codegen-ssa" in modes else False + dom_graphs = args.dom_graphs if "codegen-ssa" in modes else False if reg_alloc is None and "codegen" in args.mode: print("error: the following arguments is required: --reg-alloc") @@ -308,10 +309,10 @@ if __name__ == '__main__': main(args.filename, reg_alloc, mode, typecheck, to_stdout, outfile, args.debug, - graphs, ssa_graphs) + graphs, ssa_graphs, dom_graphs) except MiniCUnsupportedError as e: print(e) exit(5) except (MiniCInternalError, AllocationError): - traceback.print_exc() + print_exc() exit(4) diff --git a/MiniC/README-SSA.md b/MiniC/README-SSA.md new file mode 100644 index 0000000..7b2e8c3 --- /dev/null +++ b/MiniC/README-SSA.md @@ -0,0 +1,20 @@ +# MiniC Compiler +LAB5a (Control Flow Graph in SSA Form) & LAB5b (Smart Register Allocation), CAP 2022-23 + +# Authors + +YOUR NAME HERE + +# Contents + +TODO: +- Explain any design choices you may have made. +- Do not forget to remove all debug traces from your code! + +# Test design + +TODO: give the main objectives of your tests. + +# Known bugs + +TODO: bugs you could not fix (if any). diff --git a/MiniC/TP05/EnterSSA.py b/MiniC/TP05/EnterSSA.py new file mode 100644 index 0000000..b79937d --- /dev/null +++ b/MiniC/TP05/EnterSSA.py @@ -0,0 +1,70 @@ +""" +CAP, SSA Intro, Elimination and Optimisations +Functions to convert a CFG into SSA Form. +""" + +from typing import List, Dict, Set +from Lib.CFG import Block, CFG +from Lib.Operands import Renamer +from Lib.Statement import Instruction +from Lib.PhiNode import PhiNode +from Lib.Dominators import computeDom, computeDT, computeDF + + +def insertPhis(cfg: CFG, DF: Dict[Block, Set[Block]]) -> None: + """ + `insertPhis(CFG, DF)` inserts phi nodes in `cfg` where needed. + At this point, phi nodes will look like `temp_x = φ(temp_x, ..., temp_x)`. + + This is an helper function called during SSA entry. + """ + for var, defs in cfg.gather_defs().items(): + has_phi: Set[Block] = set() + queue: List[Block] = list(defs) + while queue: + d = queue.pop(0) + for b in DF[d]: + if b not in has_phi: + # TODO add a phi node in block `b` (Lab 5a, Exercise 4) + raise NotImplementedError("insertPhis") + + +def rename_block(cfg: CFG, DT: Dict[Block, Set[Block]], renamer: Renamer, b: Block) -> None: + """ + Rename variables from block b. + + This is an auxiliary function for `rename_variables`. + """ + renamer = renamer.copy() + for i in b.get_all_statements(): + if isinstance(i, Instruction | PhiNode): + i.rename(renamer) + for succ in cfg.out_blocks(b): + for i in succ._phis: + assert (isinstance(i, PhiNode)) + i.rename_from(renamer, b.get_label()) + # TODO recursive call(s) of rename_block (Lab 5a, Exercise 5) + + +def rename_variables(cfg: CFG, DT: Dict[Block, Set[Block]]) -> None: + """ + Rename variables in the CFG, to transform `temp_x = φ(temp_x, ..., temp_x)` + into `temp_x = φ(temp_0, ... temp_n)`. + + This is an helper function called during SSA entry. + """ + renamer = Renamer(cfg.fdata._pool) + # TODO initial call(s) to rename_block (Lab 5a, Exercise 5) + + +def enter_ssa(cfg: CFG, dom_graphs=False, basename="prog") -> None: + """ + Convert the CFG `cfg` into SSA Form: + compute the dominance frontier, then insert phi nodes and finally + rename variables accordingly. + + `dom_graphs` indicates if we have to print the domination graphs. + `basename` is used for the names of the produced graphs. + """ + # TODO implement this function (Lab 5a, Exercise 2) + raise NotImplementedError("enter_ssa") diff --git a/MiniC/TP05/ExitSSA.py b/MiniC/TP05/ExitSSA.py new file mode 100644 index 0000000..5b385d5 --- /dev/null +++ b/MiniC/TP05/ExitSSA.py @@ -0,0 +1,48 @@ +""" +CAP, SSA Intro, Elimination and Optimisations +Functions to convert a CFG out of SSA Form. +""" + +from typing import cast, List, Set, Tuple +from Lib import RiscV +from Lib.Graphes import DiGraph +from Lib.CFG import Block, BlockInstr, CFG +from Lib.Operands import ( + Register, DataLocation, + Temporary) +from Lib.Statement import AbsoluteJump +from Lib.Terminator import BranchingTerminator, Return +from Lib.PhiNode import PhiNode + + +def generate_moves_from_phis(phis: List[PhiNode], parent: Block) -> List[BlockInstr]: + """ + `generate_moves_from_phis(phis, parent)` builds a list of move instructions + to be inserted in a new block between `parent` and the block with phi nodes + `phis`. + + This is an helper function called during SSA exit. + """ + moves: List[BlockInstr] = [] + # TODO compute 'moves', a list of 'mv' instructions to insert under parent + # (Lab 5a, Exercise 6) + return moves + + +def exit_ssa(cfg: CFG, is_smart: bool) -> None: + """ + `exit_ssa(cfg)` replaces phi nodes with move instructions to exit SSA form. + + `is_smart` is set to true when smart register allocation is enabled (Lab 5b). + """ + for b in cfg.get_blocks(): + phis = cast(List[PhiNode], b._phis) # Use cast for Pyright + b._phis = [] # Remove all phi nodes in the block + parents: List[Block] = b.get_in().copy() # Copy as we modify it by adding blocks + for parent in parents: + moves = generate_moves_from_phis(phis, parent) + # TODO Add the block containing 'moves' to 'cfg' + # and update edges and jumps accordingly (Lab 5a, Exercise 6) + raise NotImplementedError("exit_ssa") + + diff --git a/MiniC/TP05/tests/students/README.md b/MiniC/TP05/tests/students/README.md new file mode 100644 index 0000000..f7c87d9 --- /dev/null +++ b/MiniC/TP05/tests/students/README.md @@ -0,0 +1 @@ +Add your own tests in this directory. \ No newline at end of file diff --git a/MiniC/TP05/tp5a.pdf b/MiniC/TP05/tp5a.pdf new file mode 100644 index 0000000..30a76fb Binary files /dev/null and b/MiniC/TP05/tp5a.pdf differ diff --git a/PLANNING.md b/PLANNING.md index 8fd5186..89d1b92 100644 --- a/PLANNING.md +++ b/PLANNING.md @@ -76,9 +76,17 @@ _Academic first semester 2022-2023_ * Control Flow Graph [TP04b](MiniC/TP04/tp4b.pdf). * Code in [MiniC/TP04/](MiniC/TP04/). - * Documentation [here](docs/index.html). + * Documentation (updated) [here](docs/index.html). - :book: 7th Course session: Friday 14/10/2022, 10:15. Amphi B (Gabriel Radanne) * SSA [slides in english](course/cap_cours06a_ssa.pdf). +# Week 7: + +- :hammer: Lab 5a: Wednesday 19/10/2021, 10h15-12h15. Rooms A1 (Nicolas Chappe) & B2 (Rémi Di Guardia) + + * Control Flow Graph under SSA Form [TP05a](MiniC/TP05/tp5a.pdf). + * Code in [MiniC/TP05/](MiniC/TP05/). + * Documentation (updated) [here](docs/index.html). + diff --git a/dm_cap.pdf b/dm_cap.pdf new file mode 100644 index 0000000..52f67f3 Binary files /dev/null and b/dm_cap.pdf differ diff --git a/docs/_modules/Lib/Allocator.html b/docs/_modules/Lib/Allocator.html index 741511f..116f527 100644 --- a/docs/_modules/Lib/Allocator.html +++ b/docs/_modules/Lib/Allocator.html @@ -42,10 +42,13 @@
  • Base library - RISC-V instructions
  • Base library - Operands
  • Base library - Function data
  • +
  • Base library - Graphs
  • Linear intermediate representation
  • Temporary allocation
  • Control Flow Graph - CFG and Basic blocks
  • Control Flow Graph - Terminators
  • +
  • SSA form - Dominance frontier
  • +
  • SSA form - Phi Nodes
  • diff --git a/docs/_modules/Lib/CFG.html b/docs/_modules/Lib/CFG.html index bc7a4cd..b937f3f 100644 --- a/docs/_modules/Lib/CFG.html +++ b/docs/_modules/Lib/CFG.html @@ -42,10 +42,13 @@
  • Base library - RISC-V instructions
  • Base library - Operands
  • Base library - Function data
  • +
  • Base library - Graphs
  • Linear intermediate representation
  • Temporary allocation
  • Control Flow Graph - CFG and Basic blocks
  • Control Flow Graph - Terminators
  • +
  • SSA form - Dominance frontier
  • +
  • SSA form - Phi Nodes
  • @@ -334,7 +337,6 @@ # nodes for name, blk in self._blocks.items(): if DF is not None: - print(str(name), blk._label) df_str = "{}" if blk not in DF or not len(DF[blk]) else str(DF[blk]) df_lab = blk.to_dot() + "\n\nDominance frontier:\n" + df_str else: diff --git a/docs/_modules/Lib/Dominators.html b/docs/_modules/Lib/Dominators.html new file mode 100644 index 0000000..94f6650 --- /dev/null +++ b/docs/_modules/Lib/Dominators.html @@ -0,0 +1,236 @@ + + + + + + Lib.Dominators — MiniC documentation + + + + + + + + + + + + + + + +
    + + +
    + +
    +
    +
    + +
    +
    +
    +
    + +

    Source code for Lib.Dominators

    +"""
    +Utility functions to work with dominators in a :py:class:`CFG <Lib.CFG.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
    +
    +
    +
    [docs]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
    + + +
    [docs]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)
    + + +
    [docs]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 + + +
    [docs]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
    +
    + +
    +
    + +
    +
    +
    +
    + + + + \ No newline at end of file diff --git a/docs/_modules/Lib/Errors.html b/docs/_modules/Lib/Errors.html index 0d94674..091dc1f 100644 --- a/docs/_modules/Lib/Errors.html +++ b/docs/_modules/Lib/Errors.html @@ -42,10 +42,13 @@
  • Base library - RISC-V instructions
  • Base library - Operands
  • Base library - Function data
  • +
  • Base library - Graphs
  • Linear intermediate representation
  • Temporary allocation
  • Control Flow Graph - CFG and Basic blocks
  • Control Flow Graph - Terminators
  • +
  • SSA form - Dominance frontier
  • +
  • SSA form - Phi Nodes
  • diff --git a/docs/_modules/Lib/FunctionData.html b/docs/_modules/Lib/FunctionData.html index 5a19688..1ce3b61 100644 --- a/docs/_modules/Lib/FunctionData.html +++ b/docs/_modules/Lib/FunctionData.html @@ -42,10 +42,13 @@
  • Base library - RISC-V instructions
  • Base library - Operands
  • Base library - Function data
  • +
  • Base library - Graphs
  • Linear intermediate representation
  • Temporary allocation
  • Control Flow Graph - CFG and Basic blocks
  • Control Flow Graph - Terminators
  • +
  • SSA form - Dominance frontier
  • +
  • SSA form - Phi Nodes
  • diff --git a/docs/_modules/Lib/Graphes.html b/docs/_modules/Lib/Graphes.html new file mode 100644 index 0000000..b92f014 --- /dev/null +++ b/docs/_modules/Lib/Graphes.html @@ -0,0 +1,414 @@ + + + + + + Lib.Graphes — MiniC documentation + + + + + + + + + + + + + + + +
    + + +
    + +
    +
    +
    + +
    +
    +
    +
    + +

    Source code for Lib.Graphes

    +""" Python Classes for Oriented and Non Oriented Graphs
    +"""
    +
    +from graphviz import Digraph  # for dot output
    +from typing import List, Dict, Set, Tuple, Any
    +
    +
    +
    [docs]class GraphError(Exception): + """Exception raised for self loops. + """ + + message: str + + def __init__(self, message: str): + self.message = message
    + + +
    [docs]class GeneralGraph(object): + """ + General class regrouping similarities + between directed and non oriented graphs. + The only differences between the two are: + + - how to compute the set of edges + - how to add an edge + - how to print the graph + - how to delete a vertex + - how to delete an edge + - we only color undirected graphs + """ + + graph_dict: Dict[Any, Set] + + def __init__(self, graph_dict=None): + """ + Initializes a graph object. + If no dictionary or None is given, + an empty dictionary will be used. + """ + if graph_dict is None: + graph_dict = {} + self.graph_dict = graph_dict + +
    [docs] def vertices(self) -> List[Any]: + """Return the vertices of a graph.""" + return list(self.graph_dict.keys())
    + +
    [docs] def add_vertex(self, vertex: Any) -> None: + """ + If the vertex "vertex" is not in + self.graph_dict, a key "vertex" with an empty + list as a value is added to the dictionary. + Otherwise nothing has to be done. + """ + if vertex not in self.graph_dict: + self.graph_dict[vertex] = set()
    + +
    [docs] def edges(self) -> List[Set]: + """Return the edges of the graph.""" + return []
    + + def __str__(self): + res = "vertices: " + for k in self.graph_dict: + res += str(k) + " " + res += "\nedges: " + for edge in self.edges(): + res += str(edge) + " " + return res + +
    [docs] def dfs_traversal(self, root: Any) -> List[Any]: + """ + Compute a depth first search of the graph, + from the vertex root. + """ + seen: List[Any] = [] + todo: List[Any] = [root] + while len(todo) > 0: # while todo ... + current = todo.pop() + seen.append(current) + for neighbour in self.graph_dict[current]: + if neighbour not in seen: + todo.append(neighbour) + return seen
    + +
    [docs] def is_reachable_from(self, v1: Any, v2: Any) -> bool: + """True if there is a path from v1 to v2.""" + return v2 in self.dfs_traversal(v1)
    + +
    [docs] def connected_components(self) -> List[List[Any]]: + """ + Compute the list of all connected components of the graph, + each component being a list of vetices. + """ + components: List[List[Any]] = [] + done: List[Any] = [] + for v in self.vertices(): + if v not in done: + v_comp = self.dfs_traversal(v) + components.append(v_comp) + done.extend(v_comp) + return components
    + +
    [docs] def bfs_traversal(self, root: Any) -> List[Any]: + """ + Compute a breadth first search of the graph, + from the vertex root. + """ + seen: List[Any] = [] + todo: List[Any] = [root] + while len(todo) > 0: # while todo ... + current = todo.pop(0) # list.pop(0): for dequeuing (on the left...) ! + seen.append(current) + for neighbour in self.graph_dict[current]: + if neighbour not in seen: + todo.append(neighbour) + return seen
    + + +
    [docs]class Graph(GeneralGraph): + """Class for non oriented graphs.""" + +
    [docs] def edges(self) -> List[Set]: + """ + A static method generating the set of edges + (they appear twice in the dictionnary). + Return a list of sets. + """ + edges = [] + for vertex in self.graph_dict: + for neighbour in self.graph_dict[vertex]: + if {neighbour, vertex} not in edges: + edges.append({vertex, neighbour}) + return edges
    + +
    [docs] def add_edge(self, edge: Tuple[Any, Any]) -> None: + """ + Add an edge in the graph. + edge should be a pair and not (c,c) + (we call g.add_edge((v1,v2))) + """ + (vertex1, vertex2) = edge + if vertex1 == vertex2: + raise GraphError("Cannot add a self loop on vertex {} in an unoriented graph.".format( + str(vertex1))) + if vertex1 in self.graph_dict: + self.graph_dict[vertex1].add(vertex2) + else: + self.graph_dict[vertex1] = {vertex2} + if vertex2 in self.graph_dict: + self.graph_dict[vertex2].add(vertex1) + else: + self.graph_dict[vertex2] = {vertex1}
    + +
    [docs] def print_dot(self, name: str, colors={}) -> None: + """Print the graph.""" + color_names = ['red', 'blue', 'green', 'yellow', 'cyan', 'magenta'] + \ + [f"grey{i}" for i in range(0, 100, 10)] + color_shapes = ['ellipse', 'polygon', 'box', 'circle', 'egg', 'pentagon', 'hexagon'] + dot = Digraph(comment='Conflict Graph') + for k in self.graph_dict: + shape = None + if not colors: + color = "red" # Graph not colored: red for everyone + elif k not in colors: + color = "grey" # Node not colored: grey + else: + n = colors[k] + if n < len(color_names): + color = color_names[colors[k]] + else: + color = "black" # Too many colors anyway, it won't be readable. + shape = color_shapes[n % len(color_shapes)] + dot.node(str(k), color=color, shape=shape) + for (v1, v2) in self.edges(): + dot.edge(str(v1), str(v2), dir="none") + # print(dot.source) + dot.render(name, view=True) # print in pdf
    + +
    [docs] def delete_vertex(self, vertex: Any) -> None: + """Delete a vertex and all the adjacent edges.""" + gdict = self.graph_dict + for neighbour in gdict[vertex]: + gdict[neighbour].remove(vertex) + del gdict[vertex]
    + +
    [docs] def delete_edge(self, edge: Tuple[Any, Any]): + """Delete an edge.""" + (v1, v2) = edge + self.graph_dict[v1].remove(v2) + self.graph_dict[v2].remove(v1)
    + +
    [docs] def color(self) -> Dict[Any, int]: + """ + Color the graph with an unlimited number of colors. + Return a dict vertex -> color, where color is an integer (0, 1, ...). + """ + coloring, _, _ = self.color_with_k_colors() + return coloring
    + + # see algo of the course +
    [docs] def color_with_k_colors(self, K=None, avoidingnodes=()) -> Tuple[Dict[Any, int], bool, List]: + """ + Color with <= K colors (if K is unspecified, use unlimited colors). + + Return 3 values: + + - a dict vertex -> color + - a Boolean, True if the coloring succeeded + - the set of nodes actually colored + + Do not color vertices belonging to avoidingnodes. + + Continue even if the algo fails. + """ + if K is None: + K = len(self.graph_dict) + todo_vertices = [] + is_total = True + gcopy = Graph(self.graph_dict.copy()) + # suppress nodes that are not to be considered. + for node in avoidingnodes: + gcopy.delete_vertex(node) + # append nodes in the list according to their degree and node number: + while gcopy.graph_dict: + todo = list(gcopy.graph_dict) + todo.sort(key=lambda v: (len(gcopy.graph_dict[v]), str(v))) + lower = todo[0] + todo_vertices.append(lower) + gcopy.delete_vertex(lower) + # Now reverse the list: first elements are those with higher degree + # print(todo_vertices) + todo_vertices.reverse() # in place reversal + # print(todo_vertices) + coloring = {} + colored_nodes = [] + # gdict will be the coloring map to return + gdict = self.graph_dict + for v in todo_vertices: + seen_neighbours = [x for x in gdict[v] if x in coloring] + choose_among = [i for i in range(K) if not ( + i in [coloring[v1] for v1 in seen_neighbours])] + if choose_among: + # if the node can be colored, I choose the minimal color. + color = min(choose_among) + coloring[v] = color + colored_nodes.append(v) + else: + # if I cannot color some node, the coloring is not Total + # but I continue + is_total = False + return (coloring, is_total, colored_nodes)
    + + +
    [docs]class DiGraph(GeneralGraph): + """Class for directed graphs.""" + +
    [docs] def neighbourhoods(self) -> List[Tuple[Any, Set]]: + """Return all neighbourhoods in the graph.""" + return list(self.graph_dict.items())
    + +
    [docs] def edges(self) -> List[Tuple[Any, Any]]: + """ A static method generating the set of edges""" + edges = [] + for vertex in self.graph_dict: + for neighbour in self.graph_dict[vertex]: + edges.append((vertex, neighbour)) + return edges
    + +
    [docs] def add_edge(self, edge: Tuple[Any, Any]) -> None: + """ + Add an edge in the graph. + edge should be a pair and not (c,c) + (we call g.add_edge((v1,v2))) + """ + (vertex1, vertex2) = edge + if vertex1 in self.graph_dict: + self.graph_dict[vertex1].add(vertex2) + else: + self.graph_dict[vertex1] = {vertex2} + if vertex2 not in self.graph_dict: + self.graph_dict[vertex2] = set()
    + +
    [docs] def print_dot(self, name: str) -> None: + """Print the graph.""" + dot = Digraph(comment='Conflict Graph') + for k in self.graph_dict: + shape = None + color = "grey" + dot.node(str(k), color=color, shape=shape) + for (v1, v2) in self.edges(): + dot.edge(str(v1), str(v2), dir="none") + # print(dot.source) + dot.render(name, view=True) # print in pdf
    + +
    [docs] def delete_vertex(self, vertex: Any) -> None: + """Delete a vertex and all the adjacent edges.""" + for node, neighbours in self.graph_dict.items(): + if vertex in neighbours: + neighbours.remove(vertex) + del self.graph_dict[vertex]
    + +
    [docs] def delete_edge(self, edge: Tuple[Any, Any]) -> None: + """Delete an edge.""" + (v1, v2) = edge + self.graph_dict[v1].remove(v2)
    +
    + +
    +
    + +
    +
    +
    +
    + + + + \ No newline at end of file diff --git a/docs/_modules/Lib/LinearCode.html b/docs/_modules/Lib/LinearCode.html index cc350fe..7c04838 100644 --- a/docs/_modules/Lib/LinearCode.html +++ b/docs/_modules/Lib/LinearCode.html @@ -42,10 +42,13 @@
  • Base library - RISC-V instructions
  • Base library - Operands
  • Base library - Function data
  • +
  • Base library - Graphs
  • Linear intermediate representation
  • Temporary allocation
  • Control Flow Graph - CFG and Basic blocks
  • Control Flow Graph - Terminators
  • +
  • SSA form - Dominance frontier
  • +
  • SSA form - Phi Nodes
  • diff --git a/docs/_modules/Lib/Operands.html b/docs/_modules/Lib/Operands.html index 1fcfa16..f8a089f 100644 --- a/docs/_modules/Lib/Operands.html +++ b/docs/_modules/Lib/Operands.html @@ -42,10 +42,13 @@
  • Base library - RISC-V instructions
  • Base library - Operands
  • Base library - Function data
  • +
  • Base library - Graphs
  • Linear intermediate representation
  • Temporary allocation
  • Control Flow Graph - CFG and Basic blocks
  • Control Flow Graph - Terminators
  • +
  • SSA form - Dominance frontier
  • +
  • SSA form - Phi Nodes
  • @@ -204,10 +207,10 @@ RA = Register(1) #: SP = Register(2) -#: -GP = Register(3) # Register not used for this course -#: -TP = Register(4) # Register not used for this course +#: Register not used for this course +GP = Register(3) +#: Register not used for this course +TP = Register(4) #: A = tuple(Register(i + 10) for i in range(8)) #: @@ -220,8 +223,8 @@ A0 = A[0] # function args/return Values: A0, A1 #: A1 = A[1] -#: -FP = S[0] # Frame Pointer = Saved register 0 +#: Frame Pointer = Saved register 0 +FP = S[0] #: General purpose registers, usable for the allocator GP_REGS = S[4:] + T # s0, s1, s2 and s3 are special diff --git a/docs/_modules/Lib/PhiNode.html b/docs/_modules/Lib/PhiNode.html new file mode 100644 index 0000000..762d22e --- /dev/null +++ b/docs/_modules/Lib/PhiNode.html @@ -0,0 +1,166 @@ + + + + + + Lib.PhiNode — MiniC documentation + + + + + + + + + + + + + + + +
    + + +
    + +
    +
    +
    + +
    +
    +
    +
    + +

    Source code for Lib.PhiNode

    +"""
    +Classes for φ nodes in a RiscV CFG :py:class:`CFG <Lib.CFG.CFG>` under SSA Form:
    +:py:class:`PhiNode` for a statement of the form temp_x = φ(temp_0, ..., temp_n).
    +These particular kinds of statements are expected to be in the field
    +b._phis for a :py:class:`Block <Lib.CFG.Block>` b.
    +"""
    +
    +from dataclasses import dataclass
    +from typing import Dict
    +
    +from Lib.Operands import Operand, Temporary, DataLocation, Renamer
    +from Lib.Statement import Statement, Label
    +
    +
    +
    [docs]@dataclass +class PhiNode(Statement): + """ + A φ node is a renaming in the CFG, of the form temp_x = φ(temp_0, ..., temp_n). + The field var contains the variable temp_x. + The field srcs relies for each precedent block in the CFG, identified with its label, + the variable temp_i of the φ node. + """ + var: DataLocation + srcs: Dict[Label, Operand] + +
    [docs] def defined(self): + """Return the variable defined by the φ node.""" + return [self.var]
    + +
    [docs] def used(self) -> Dict[Label, Operand]: + """ + Return the dictionnary associating for each previous block the corresponding variable. + """ + return self.srcs
    + +
    [docs] def rename(self, renamer: Renamer) -> None: + """Rename the variable defined by the φ node with a fresh name.""" + if isinstance(self.var, Temporary): + self.var = renamer.fresh(self.var)
    + +
    [docs] def rename_from(self, renamer: Renamer, label: Label) -> None: + """Rename the variable associated to the block identified by `label`.""" + if label in self.srcs: + t = self.srcs[label] + if isinstance(t, Temporary): + if renamer.defined(t): + self.srcs[label] = renamer.replace(t) + else: + del self.srcs[label]
    + + def __str__(self): + return "{} = φ({})".format(self.var, self.srcs) + + def __hash__(self): + return hash((self.var, *self.srcs.items())) + +
    [docs] def printIns(self, stream): + print(' # ' + str(self), file=stream)
    +
    + +
    +
    + +
    +
    +
    +
    + + + + \ No newline at end of file diff --git a/docs/_modules/Lib/RiscV.html b/docs/_modules/Lib/RiscV.html index 6f37f75..d05ee36 100644 --- a/docs/_modules/Lib/RiscV.html +++ b/docs/_modules/Lib/RiscV.html @@ -42,10 +42,13 @@
  • Base library - RISC-V instructions
  • Base library - Operands
  • Base library - Function data
  • +
  • Base library - Graphs
  • Linear intermediate representation
  • Temporary allocation
  • Control Flow Graph - CFG and Basic blocks
  • Control Flow Graph - Terminators
  • +
  • SSA form - Dominance frontier
  • +
  • SSA form - Phi Nodes
  • @@ -79,8 +82,7 @@ """ from Lib.Errors import MiniCInternalError -from Lib.Operands import ( - Condition, Immediate, Operand, Function, ZERO) +from Lib.Operands import (Condition, Immediate, Operand, Function) from Lib.Statement import (Instru3A, AbsoluteJump, ConditionalJump, Label) @@ -99,7 +101,6 @@ This is a wrapper around bge, bgt, beq, ... c is a Condition, like Condition('bgt'), Condition(MiniCParser.EQ), ... """ - op2 = op2 if op2 != Immediate(0) else ZERO return ConditionalJump(cond=cond, op1=op1, op2=op2, label=label) diff --git a/docs/_modules/Lib/Statement.html b/docs/_modules/Lib/Statement.html index 3160cdb..9cb51cd 100644 --- a/docs/_modules/Lib/Statement.html +++ b/docs/_modules/Lib/Statement.html @@ -42,10 +42,13 @@
  • Base library - RISC-V instructions
  • Base library - Operands
  • Base library - Function data
  • +
  • Base library - Graphs
  • Linear intermediate representation
  • Temporary allocation
  • Control Flow Graph - CFG and Basic blocks
  • Control Flow Graph - Terminators
  • +
  • SSA form - Dominance frontier
  • +
  • SSA form - Phi Nodes
  • @@ -177,7 +180,7 @@ defs = [self.args()[0]] return defs -
    [docs] def used(self): +
    [docs] def used(self) -> List[Operand]: if self.is_read_only(): uses = self.args() else: diff --git a/docs/_modules/Lib/Terminator.html b/docs/_modules/Lib/Terminator.html index 8cd4e5f..e623f0d 100644 --- a/docs/_modules/Lib/Terminator.html +++ b/docs/_modules/Lib/Terminator.html @@ -42,10 +42,13 @@
  • Base library - RISC-V instructions
  • Base library - Operands
  • Base library - Function data
  • +
  • Base library - Graphs
  • Linear intermediate representation
  • Temporary allocation
  • Control Flow Graph - CFG and Basic blocks
  • Control Flow Graph - Terminators
  • +
  • SSA form - Dominance frontier
  • +
  • SSA form - Phi Nodes
  • @@ -125,7 +128,10 @@ raise Exception( "substitute: No possible substitution on instruction {}" .format(self)) - return self
    + return self + +
    [docs] def is_read_only(self): + return True
    [docs]@dataclass(init=False) @@ -201,7 +207,7 @@ return BranchingTerminator(j.cond, j.op1, j.op2, j.label, label_else) case AbsoluteJump(): return AbsoluteJump(label=j.label) - case None: + case _: if next_label: return AbsoluteJump(next_label) else: diff --git a/docs/_modules/index.html b/docs/_modules/index.html index 4dc1e00..111bbb7 100644 --- a/docs/_modules/index.html +++ b/docs/_modules/index.html @@ -42,10 +42,13 @@
  • Base library - RISC-V instructions
  • Base library - Operands
  • Base library - Function data
  • +
  • Base library - Graphs
  • Linear intermediate representation
  • Temporary allocation
  • Control Flow Graph - CFG and Basic blocks
  • Control Flow Graph - Terminators
  • +
  • SSA form - Dominance frontier
  • +
  • SSA form - Phi Nodes
  • @@ -74,10 +77,13 @@

    All modules for which code is available

    • Lib.Allocator
    • Lib.CFG
    • +
    • Lib.Dominators
    • Lib.Errors
    • Lib.FunctionData
    • +
    • Lib.Graphes
    • Lib.LinearCode
    • Lib.Operands
    • +
    • Lib.PhiNode
    • Lib.RiscV
    • Lib.Statement
    • Lib.Terminator
    • diff --git a/docs/_sources/api/Lib.Dominators.rst.txt b/docs/_sources/api/Lib.Dominators.rst.txt new file mode 100644 index 0000000..045e19e --- /dev/null +++ b/docs/_sources/api/Lib.Dominators.rst.txt @@ -0,0 +1,7 @@ +Lib.Dominators module +===================== + +.. automodule:: Lib.Dominators + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/_sources/api/Lib.Graphes.rst.txt b/docs/_sources/api/Lib.Graphes.rst.txt new file mode 100644 index 0000000..d146ef2 --- /dev/null +++ b/docs/_sources/api/Lib.Graphes.rst.txt @@ -0,0 +1,7 @@ +Lib.Graphes module +================== + +.. automodule:: Lib.Graphes + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/_sources/api/Lib.PhiNode.rst.txt b/docs/_sources/api/Lib.PhiNode.rst.txt new file mode 100644 index 0000000..b2ab18d --- /dev/null +++ b/docs/_sources/api/Lib.PhiNode.rst.txt @@ -0,0 +1,7 @@ +Lib.PhiNode module +================== + +.. automodule:: Lib.PhiNode + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/_sources/api/Lib.rst.txt b/docs/_sources/api/Lib.rst.txt index ed2c967..e81f7fb 100644 --- a/docs/_sources/api/Lib.rst.txt +++ b/docs/_sources/api/Lib.rst.txt @@ -9,10 +9,13 @@ Submodules Lib.Allocator Lib.CFG + Lib.Dominators Lib.Errors Lib.FunctionData + Lib.Graphes Lib.LinearCode Lib.Operands + Lib.PhiNode Lib.RiscV Lib.Statement Lib.Terminator diff --git a/docs/_sources/index.rst.txt b/docs/_sources/index.rst.txt index 5514932..ebe38e6 100644 --- a/docs/_sources/index.rst.txt +++ b/docs/_sources/index.rst.txt @@ -15,10 +15,13 @@ Welcome to MiniC's documentation! Base library - RISC-V instructions Base library - Operands Base library - Function data + Base library - Graphs Linear intermediate representation Temporary allocation Control Flow Graph - CFG and Basic blocks Control Flow Graph - Terminators + SSA form - Dominance frontier + SSA form - Phi Nodes These pages document the various Python sources in the Lib/ folder of MiniC. You should not have to edit them *at all*. @@ -36,6 +39,9 @@ and pseudo-instructions, we give you the :doc:`api/Lib.RiscV`. RISC-V instructions take arguments of various kinds, as defined in the :doc:`api/Lib.Operands`. +At some point, we will need some basic functions about oriented and non oriented graphs, +those are present in :doc:`api/Lib.Graphes`. + Linear Intermediate representation ---------------------------------- @@ -54,6 +60,15 @@ Control Flow Graph Intermediate representation The classes for the CFG and its basic blocks are in the :doc:`api/Lib.CFG`. Each block ends with a terminator, as documented in the :doc:`api/Lib.Terminator`. +SSA form +-------- + +The translation of the CFG into SSA form makes use of dominance frontiers. +Functions to work with dominance are defined in the :doc:`api/Lib.Dominators`. + +Phi nodes, a special kind of statement that appears in CFGs in SSA form, +are defined in the :doc:`api/Lib.PhiNode`. + Indices and tables ================== diff --git a/docs/api/Lib.Allocator.html b/docs/api/Lib.Allocator.html index 0691e4d..cdd8824 100644 --- a/docs/api/Lib.Allocator.html +++ b/docs/api/Lib.Allocator.html @@ -45,10 +45,13 @@
    • Base library - RISC-V instructions
    • Base library - Operands
    • Base library - Function data
    • +
    • Base library - Graphs
    • Linear intermediate representation
    • Temporary allocation
    • Control Flow Graph - CFG and Basic blocks
    • Control Flow Graph - Terminators
    • +
    • SSA form - Dominance frontier
    • +
    • SSA form - Phi Nodes
    diff --git a/docs/api/Lib.CFG.html b/docs/api/Lib.CFG.html index 157921e..26ea8b4 100644 --- a/docs/api/Lib.CFG.html +++ b/docs/api/Lib.CFG.html @@ -45,10 +45,13 @@
  • Base library - RISC-V instructions
  • Base library - Operands
  • Base library - Function data
  • +
  • Base library - Graphs
  • Linear intermediate representation
  • Temporary allocation
  • Control Flow Graph - CFG and Basic blocks
  • Control Flow Graph - Terminators
  • +
  • SSA form - Dominance frontier
  • +
  • SSA form - Phi Nodes
  • diff --git a/docs/api/Lib.Dominators.html b/docs/api/Lib.Dominators.html new file mode 100644 index 0000000..d6970d2 --- /dev/null +++ b/docs/api/Lib.Dominators.html @@ -0,0 +1,152 @@ + + + + + + + Lib.Dominators module — MiniC documentation + + + + + + + + + + + + + + + + + +
    + + +
    + +
    +
    +
    + +
    +
    +
    +
    + +
    +

    Lib.Dominators module

    +

    Utility functions to work with dominators in a CFG.

    +

    Do not hesitate to look at the source of the functions +to get a better understanding of the algorithms.

    +
    +
    +Lib.Dominators.computeDom(cfg: CFG) Dict[Block, Set[Block]][source]
    +

    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.

    +
    + +
    +
    +Lib.Dominators.printDT(filename: str, graph: Dict[Block, Set[Block]]) None[source]
    +

    Display a graphical rendering of the given domination tree.

    +
    + +
    +
    +Lib.Dominators.computeDT(cfg: CFG, dominators: Dict[Block, Set[Block]], dom_graphs: bool, basename: str) Dict[Block, Set[Block]][source]
    +

    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.

    +
    + +
    +
    +Lib.Dominators.computeDF(cfg: CFG, dominators: Dict[Block, Set[Block]], DT: Dict[Block, Set[Block]], dom_graphs: bool, basename: str) Dict[Block, Set[Block]][source]
    +

    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.

    +
    + +
    + + +
    +
    + +
    +
    +
    +
    + + + + \ No newline at end of file diff --git a/docs/api/Lib.Errors.html b/docs/api/Lib.Errors.html index 84dc178..cf455a0 100644 --- a/docs/api/Lib.Errors.html +++ b/docs/api/Lib.Errors.html @@ -45,10 +45,13 @@
  • Base library - RISC-V instructions
  • Base library - Operands
  • Base library - Function data
  • +
  • Base library - Graphs
  • Linear intermediate representation
  • Temporary allocation
  • Control Flow Graph - CFG and Basic blocks
  • Control Flow Graph - Terminators
  • +
  • SSA form - Dominance frontier
  • +
  • SSA form - Phi Nodes
  • diff --git a/docs/api/Lib.FunctionData.html b/docs/api/Lib.FunctionData.html index 41523d1..ef29076 100644 --- a/docs/api/Lib.FunctionData.html +++ b/docs/api/Lib.FunctionData.html @@ -19,7 +19,7 @@ - + @@ -45,10 +45,13 @@
  • Base library - RISC-V instructions
  • Base library - Operands
  • Base library - Function data
  • +
  • Base library - Graphs
  • Linear intermediate representation
  • Temporary allocation
  • Control Flow Graph - CFG and Basic blocks
  • Control Flow Graph - Terminators
  • +
  • SSA form - Dominance frontier
  • +
  • SSA form - Phi Nodes
  • @@ -137,7 +140,7 @@ Offsets are decreasing relative to FP.