Commit TP5b

This commit is contained in:
Rémi Di Guardia
2022-10-26 09:44:46 +02:00
parent 1c151ffe93
commit a89ae6ffb7
20 changed files with 269 additions and 21 deletions
+99
View File
@@ -0,0 +1,99 @@
from typing import Dict, Set, Tuple
from Lib.Operands import Temporary
from Lib.Statement import Statement, regset_to_string
from Lib.CFG import Block, CFG
from Lib.PhiNode import PhiNode
class LivenessSSA:
"""Liveness Analysis on a CFG under SSA Form."""
def __init__(self, cfg: CFG, debug=False):
self._cfg: CFG = cfg
self._debug: bool = debug
# Temporary already propagated, by Block
self._seen: Dict[Block, Set[Temporary]] = dict()
# Live Temporary at outputs of Statement
self._liveout: Dict[Statement, Set[Temporary]] = dict()
def run(self) -> None:
"""Compute the liveness."""
# Initialization
for block in self._cfg.get_blocks():
self._seen[block] = set()
for instr in block.get_all_statements():
self._liveout[instr] = set()
# Start the use-def chains
for var, uses in self.gather_uses().items():
for block, pos, instr in uses:
self.live_start(block, pos, instr, var)
# Add conflicts on phis
self.conflict_on_phis()
# Final debugging print
if self._debug:
self.print_map_in_out()
def live_start(self, block: Block, pos: int | None,
s: Statement, var: Temporary) -> None:
"""Start backward propagation of liveness information."""
if isinstance(s, PhiNode):
assert(pos is None)
for label, var_phi in s.used().items():
if var_phi == var:
prev_block = self._cfg.get_block(label)
self.liveout_at_block(prev_block, var)
else:
assert(pos is not None)
self.livein_at_instruction(block, pos, var)
def liveout_at_block(self, block: Block, var: Temporary) -> None:
"""Backward propagation of liveness information at a block."""
raise NotImplementedError("LivenessSSA") # TODO (Lab 5b, Exercise 1)
def liveout_at_instruction(self, block: Block, pos: int, var: Temporary) -> None:
"""Backward propagation of liveness information at a non-phi instruction."""
instr = block.get_body_and_terminator()[pos]
raise NotImplementedError("LivenessSSA") # TODO (Lab 5b, Exercise 1)
def livein_at_instruction(self, block: Block, pos: int, var: Temporary) -> None:
"""Backward propagation of liveness information at a non-phi instruction."""
raise NotImplementedError("LivenessSSA") # TODO (Lab 5b, Exercise 1)
def gather_uses(self) -> Dict[Temporary, Set[Tuple[Block, int | None, Statement]]]:
"""
Return a dictionnary giving for each variable the set of statements using it,
with additionnaly for each statement, the block of the statement and its position inside.
Phi instructions have position None in their block, while a Terminaor is at the last
position of its block.
"""
uses: Dict[Temporary, Set[Tuple[Block, int | None, Statement]]] = dict()
for block in self._cfg.get_blocks():
# Look inside the phi node
for instr in block._phis:
assert (isinstance(instr, PhiNode))
for var in instr.used().values():
if isinstance(var, Temporary):
var_uses = uses.get(var, set())
uses[var] = var_uses.union({(block, None, instr)})
# Look inside the body and the terminator
for pos, instr in enumerate(block.get_body_and_terminator()):
for var in instr.used():
if isinstance(var, Temporary):
var_uses = uses.get(var, set())
uses[var] = var_uses.union({(block, pos, instr)})
return uses
def conflict_on_phis(self) -> None:
"""Ensures that variables defined by phi instructions are in conflict with one-another."""
raise NotImplementedError("LivenessSSA") # TODO (Lab 5b, Exercise 1)
def print_map_in_out(self) -> None: # pragma: no cover
"""Print live out sets at each instruction, group by block, useful for debugging!"""
print("Liveout: [")
for block in self._cfg.get_blocks():
print("Block " + str(block.get_label()) + ": {\n "
+ ",\n ".join("\"{}\": {}"
.format(instr, regset_to_string(self._liveout[instr]))
for instr in block.get_all_statements()) +
"}")
print("]")
+99
View File
@@ -0,0 +1,99 @@
from typing import List, Dict
from Lib.Errors import MiniCInternalError
from Lib.Operands import Temporary, Operand, S, Register, Offset, DataLocation, GP_REGS
from Lib.Statement import Instruction
from Lib.Allocator import Allocator
from Lib.FunctionData import FunctionData
from Lib import RiscV
from Lib.Graphes import Graph # For Graph coloring utility functions
class SmartAllocator(Allocator):
_igraph: Graph # interference graph
def __init__(self, fdata: FunctionData, basename: str, liveness,
debug=False, debug_graphs=False):
self._liveness = liveness
self._basename: str = basename
self._debug: bool = debug
self._debug_graphs: bool = debug_graphs
super().__init__(fdata)
def replace(self, old_instr: Instruction) -> List[Instruction]:
"""
Replace Temporary operands with the corresponding allocated
physical register (Register) OR memory location (Offset).
"""
before: List[Instruction] = []
after: List[Instruction] = []
subst: Dict[Operand, Operand] = {}
# TODO (lab5): Compute before, after, subst. This is similar to what
# TODO (lab5): replace from the Naive and AllInMem Allocators do (Lab 4).
raise NotImplementedError("Smart Replace (lab5)") # TODO
# And now return the new list!
instr = old_instr.substitute(subst)
return before + [instr] + after
def prepare(self) -> None:
"""
Perform all preparatory steps related to smart register allocation:
- Dataflow analysis to compute the liveness range of each
temporary.
- Interference graph construction.
- Graph coloring.
- Associating temporaries with actual locations.
"""
# Liveness analysis
self._liveness.run()
# Interference graph
self.build_interference_graph()
if self._debug_graphs:
print("Printing the interference graph")
self._igraph.print_dot(self._basename + "interference.dot")
# Smart Allocation via graph coloring
self.smart_alloc()
def build_interference_graph(self) -> None:
"""
Build the interference graph (in self._igraph).
Vertices of the graph are temporaries,
and an edge exists between temporaries iff they are in conflict.
"""
self._igraph: Graph = Graph()
# Create a vertex for every temporary
# There may be temporaries the code does not use anymore,
# but it does not matter as they interfere with no one.
for v in self._fdata._pool.get_all_temps():
self._igraph.add_vertex(v)
# Iterate over self._liveness._liveout (dictionary containing all
# live out temporaries for each instruction), and for each conflict use
# self._igraph.add_edge((t1, t2)) to add the corresponding edge.
raise NotImplementedError("build_interference_graph (lab5)") # TODO
def smart_alloc(self) -> None:
"""
Allocates all temporaries via graph coloring.
Prints the colored graph if self._debug_graphs is True.
Precondition: the interference graph _igraph must have been built.
"""
# Checking the interference graph has been built
if not self._igraph:
raise MiniCInternalError("Empty interference graph in the Smart Allocator")
# Coloring of the interference graph
coloringreg: Dict[Temporary, int] = self._igraph.color()
if self._debug_graphs:
print("coloring = " + str(coloringreg))
self._igraph.print_dot(self._basename + "_colored.dot", coloringreg)
# Temporary -> DataLocation (Register or Offset) dictionary,
# specifying where a given Temporary should be allocated:
alloc_dict: Dict[Temporary, DataLocation] = dict()
# Use the coloring `coloringreg` to fill `alloc_dict`.
# Our version is less than 5 lines of code.
raise NotImplementedError("Allocation based on graph coloring (lab5)") # TODO
if self._debug:
print("Allocation:")
print(alloc_dict)
self._fdata._pool.set_temp_allocation(alloc_dict)
Binary file not shown.