Commit Lab 5a

This commit is contained in:
Rémi Di Guardia
2022-10-19 09:50:20 +02:00
parent deb7cc0b54
commit 5e8c7214d9
46 changed files with 2421 additions and 55 deletions
+128
View File
@@ -0,0 +1,128 @@
"""
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
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
+306
View File
@@ -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)
+58
View File
@@ -0,0 +1,58 @@
"""
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
@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)