Merge remote-tracking branch 'refs/remotes/origin/main'
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)
|
||||
+20
-19
@@ -5,8 +5,9 @@ Usage:
|
||||
python3 MiniCC.py --mode <mode> <filename>
|
||||
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)
|
||||
|
||||
@@ -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).
|
||||
@@ -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")
|
||||
@@ -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")
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
Add your own tests in this directory.
|
||||
Binary file not shown.
Reference in New Issue
Block a user