Commit Lab 5a
This commit is contained in:
@@ -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
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
Reference in New Issue
Block a user