Commit TP5b
This commit is contained in:
@@ -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("]")
|
||||
@@ -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.
Reference in New Issue
Block a user