diff --git a/MiniC/Lib/CFG.py b/MiniC/Lib/CFG.py new file mode 100644 index 0000000..3937259 --- /dev/null +++ b/MiniC/Lib/CFG.py @@ -0,0 +1,272 @@ +""" +Classes for a RiscV CFG: :py:class:`CFG` for the CFG itself, +and :py:class:`Block` for its basic blocks. +""" + +from graphviz import Digraph # for dot output +from typing import cast, Any, Dict, List, Set, Iterator + +from Lib.Errors import MiniCInternalError +from Lib.Operands import (Operand, Immediate, Function, A0) +from Lib.Statement import ( + Statement, Instru3A, Label, + AbsoluteJump, ConditionalJump, Comment +) +from Lib.Terminator import ( + Terminator, BranchingTerminator, Return) +from Lib.FunctionData import (FunctionData, _iter_statements, _print_code) + + +BlockInstr = Instru3A | Comment + + +class Block: + """ + A basic block of a :py:class:`CFG` is made of three main parts: + + - a start :py:class:`label ` that uniquely identifies the block in the CFG + - the main body of the block, a list of instructions + (excluding labels, jumps and branching instructions) + - a :py:class:`terminator ` + that represents the final jump or branching instruction of the block, + and points to the successors of the block. + See the documentation for :py:class:`Lib.Terminator.Terminator` for further explanations. + """ + + _terminator: Terminator + _label: Label + _phis: List[Statement] + _instructions: List[BlockInstr] + _in: List['Block'] + _gen: Set + _kill: Set + + def __init__(self, label: Label, insts: List[BlockInstr], terminator: Terminator): + self._label = label + self._instructions = insts + self._in = [] + self._phis = [] + self._terminator = terminator + self._gen = set() + self._kill = set() + + def __str__(self): + instr = [i for i in self._instructions if not isinstance(i, Comment)] + instr_str = '\n'.join(map(str, instr)) + s = '{}:\n\n{}'.format(self._label, instr_str) + return s + + def to_dot(self) -> str: # pragma: no cover + """Outputs all statements of the block as a string.""" + # dot is weird: lines ending with \l instead of \n are left-aligned. + NEWLINE = '\\l ' + instr = [] + instr += self._phis + instr += [i for i in self._instructions if not isinstance(i, Comment)] + instr += [self.get_terminator()] + instr_str = NEWLINE.join(map(str, instr)) + s = '{}:{}{}\\l'.format(self._label, NEWLINE, instr_str) + return s + + def __repr__(self): + return str(self._label) + + def get_body(self) -> List[BlockInstr]: + """Return the statements in the body of the block (no phi-node nor the terminator).""" + return self._instructions + + def get_all_statements(self) -> List[Statement]: + """ + Return all statements of the block + (including phi-nodes and the terminator, but not the label of the block). + """ + return (self._phis + + cast(List[Statement], self._instructions) + + [self.get_terminator()]) + + def get_label(self) -> Label: + """Return the label of the block.""" + return self._label + + def get_in(self) -> List['Block']: + """Return the list of blocks with an edge to the considered block.""" + return self._in + + def get_terminator(self) -> Terminator: + """Return the terminator of the block.""" + return self._terminator + + def set_terminator(self, term: Terminator) -> None: + """Set the terminator of the block.""" + self._terminator = term + + def iter_statements(self, f) -> None: + """Iterate over instructions. + For each real instruction i (not label or comment), replace it + with the list of instructions given by f(i). + + Assume there is no phi-node. + """ + assert (self._phis == []) + new_statements = _iter_statements(self._instructions, f) + end_statements = f(self.get_terminator()) + if len(end_statements) >= 1 and isinstance(end_statements[-1], Terminator): + new_terminator = end_statements.pop(-1) + self._instructions = new_statements + end_statements + self.set_terminator(new_terminator) + else: + raise MiniCInternalError( + "Block.iter_statements: Invalid replacement for terminator {}:\n {}" + .format(self.get_terminator(), end_statements)) + + def add_instruction(self, instr: BlockInstr) -> None: + """Add an instruction to the body of the block.""" + self._instructions.append(instr) + + +class CFG: + """ + A complete control-flow graph representing a function. + This class is mainly made of a list of basic :py:class:`Block`, + a label indicating the :py:meth:`entry point of the function `, + and an :py:meth:`exit label `. + + As with linear code, metadata about the function can be found + in the :py:attr:`fdata` member variable. + """ + + _start: Label + _end: Label + _blocks: Dict[Label, Block] + + #: Metadata about the function represented by this CFG + fdata: FunctionData + + def __init__(self, fdata: FunctionData): + self._blocks = {} + self.fdata = fdata + self._init_blks() + self._end = self.fdata.fresh_label("end") + + def _init_blks(self) -> None: + """Add a block for division by 0.""" + # Label for the address of the error message + # This address is added by print_code + label_div_by_zero_msg = Label(self.fdata._label_div_by_zero.name + "_msg") + blk = Block(self.fdata._label_div_by_zero, [ + Instru3A("la", A0, label_div_by_zero_msg), + Instru3A("call", Function("println_string")), + Instru3A("li", A0, Immediate(1)), + Instru3A("call", Function("exit")), + ], terminator=Return()) + self.add_block(blk) + + def get_start(self) -> Label: + """Return the entry label of the CFG.""" + return self._start + + def set_start(self, start: Label) -> None: + """Set the entry label of the CFG.""" + assert (start in self._blocks) + self._start = start + + def get_end(self) -> Label: + """Return the exit label of the CFG.""" + return self._end + + def add_block(self, blk: Block) -> None: + """Add a new block to the CFG.""" + self._blocks[blk._label] = blk + + def get_block(self, name: Label) -> Block: + """Return the block with label `name`.""" + return self._blocks[name] + + def get_blocks(self) -> List[Block]: + """Return all the blocks.""" + return [b for b in self._blocks.values()] + + def get_entries(self) -> List[Block]: + """Return all the blocks with no predecessors.""" + return [b for b in self._blocks.values() if not b.get_in()] + + def add_edge(self, src: Block, dest: Block) -> None: + """Add the edge src -> dest in the control flow graph.""" + dest.get_in().append(src) + # assert (dest.get_label() in src.get_terminator().targets()) + + def remove_edge(self, src: Block, dest: Block) -> None: + """Remove the edge src -> dest in the control flow graph.""" + dest.get_in().remove(src) + # assert (dest.get_label() not in src.get_terminator().targets()) + + def out_blocks(self, block: Block) -> List[Block]: + """ + Return the list of blocks in the CFG targeted by + the Terminator of Block block. + """ + return [self.get_block(dest) for dest in block.get_terminator().targets()] + + def gather_defs(self) -> Dict[Any, Set[Block]]: + """ + Return a dictionary associating variables to all the blocks + containing one of their definitions. + """ + defs: Dict[Operand, Set[Block]] = dict() + for b in self.get_blocks(): + for i in b.get_all_statements(): + for v in i.defined(): + if v not in defs: + defs[v] = {b} + else: + defs[v].add(b) + return defs + + def iter_statements(self, f) -> None: + """Apply f to all instructions in all the blocks.""" + for b in self.get_blocks(): + b.iter_statements(f) + + def linearize_naive(self) -> Iterator[Statement]: + """ + Linearize the given control flow graph as a list of instructions. + Naive procedure that adds jumps everywhere. + """ + for label, block in self._blocks.items(): + yield label + for i in block._instructions: + yield i + match block.get_terminator(): + case BranchingTerminator() as j: + # In case of conditional jump, add the missing edge + yield ConditionalJump(j.cond, j.op1, j.op2, j.label_then) + yield AbsoluteJump(j.label_else) + case AbsoluteJump() as j: + yield AbsoluteJump(j.label) + case Return(): + yield AbsoluteJump(self.get_end()) + + def print_code(self, output, linearize=(lambda cfg: list(cfg.linearize_naive())), + comment=None) -> None: + """Print the linearization of the CFG.""" + statements = linearize(self) + _print_code(statements, self.fdata, output, init_label=self._start, + fin_label=self._end, fin_div0=False, comment=comment) + + def print_dot(self, filename, DF=None, view=False) -> None: # pragma: no cover + """Print the CFG as a graph.""" + graph = Digraph() + # nodes + for name, blk in self._blocks.items(): + if DF is not None: + print(str(name), blk._label) + df_str = "{}" if blk not in DF or not len(DF[blk]) else str(DF[blk]) + df_lab = blk.to_dot() + "\n\nDominance frontier:\n" + df_str + else: + df_lab = blk.to_dot() + graph.node(str(blk._label), label=df_lab, shape='rectangle') + # edges + for name, blk in self._blocks.items(): + for child in blk.get_terminator().targets(): + graph.edge(str(blk._label), str(child)) + graph.render(filename, view=view) diff --git a/MiniC/Lib/Terminator.py b/MiniC/Lib/Terminator.py new file mode 100644 index 0000000..5ae5d1e --- /dev/null +++ b/MiniC/Lib/Terminator.py @@ -0,0 +1,133 @@ +""" +MIF08, CAP, CFG library - Terminators. + +Each :py:class:`block ` of a :py:class:`CFG ` +ends with a branching instruction called a terminator. +There are three kinds of terminators: + +- :py:class:`Lib.Statement.AbsoluteJump` is a non-conditional jump + to another block of the CFG +- :py:class:`BranchingTerminator` is a conditional branching + instruction with two successor blocks. + Unlike the class :py:class:`ConditionalJump ` + that was used in :py:class:`LinearCode `, + both successor labels have to be specified. +- :py:class:`Return` marks the end of the function + +During the construction of the CFG, :py:func:`jump2terminator` builds +a terminator for each extracted chunk of instructions. +""" + +from dataclasses import dataclass +from typing import List, Dict +from Lib.Errors import MiniCInternalError +from Lib.Operands import Operand, Renamer, Temporary, Condition +from Lib.Statement import AbsoluteJump, ConditionalJump, Instruction, Label, Statement + + +@dataclass(unsafe_hash=True) +class Return(Statement): + """A terminator that marks the end of the function.""" + + def __str__(self): + return ("return") + + def printIns(self, stream): + print("return", file=stream) + + def targets(self) -> List[Label]: + """Return the labels targetted by the Return terminator.""" + return [] + + def args(self) -> List[Operand]: + return [] + + def rename(self, renamer: Renamer): + pass + + def substitute(self, subst: Dict[Operand, Operand]): + if subst != {}: + raise Exception( + "substitute: No possible substitution on instruction {}" + .format(self)) + return self + + +@dataclass(init=False) +class BranchingTerminator(Instruction): + """A terminating statement with a condition.""" + + #: The condition of the branch + cond: Condition + #: The destination label if the condition is true + label_then: Label + #: The destination label if the condition is false + label_else: Label + #: The first operand of the condition + op1: Operand + #: The second operand of the condition + op2: Operand + _read_only = True + + def __init__(self, cond: Condition, op1: Operand, op2: Operand, + label_then: Label, label_else: Label): + self.cond = cond + self.label_then = label_then + self.label_else = label_else + self.op1 = op1 + self.op2 = op2 + self.ins = str(self.cond) + + def args(self) -> List[Operand]: + return [self.op1, self.op2, self.label_then, self.label_else] + + def targets(self) -> List[Label]: + """Return the labels targetted by the Branching terminator.""" + return [self.label_then, self.label_else] + + def rename(self, renamer: Renamer): + if isinstance(self.op1, Temporary): + self.op1 = renamer.replace(self.op1) + if isinstance(self.op2, Temporary): + self.op2 = renamer.replace(self.op2) + + def substitute(self, subst: Dict[Operand, Operand]): + for op in subst: + if op not in self.args(): + raise Exception( + "substitute: Operand {} is not present in instruction {}" + .format(op, self)) + op1 = subst.get(self.op1, self.op1) if isinstance(self.op1, Temporary) \ + else self.op1 + op2 = subst.get(self.op2, self.op2) if isinstance(self.op2, Temporary) \ + else self.op2 + return BranchingTerminator(self.cond, op1, op2, self.label_then, self.label_else) + + def __hash__(self): + return hash(super) + + +Terminator = Return | AbsoluteJump | BranchingTerminator + + +def jump2terminator(j: ConditionalJump | AbsoluteJump | None, + next_label: Label | None) -> Terminator: + """ + Construct the Terminator associated to the potential jump j + to the potential label next_label. + """ + match j: + case ConditionalJump(): + if (next_label is None): + raise MiniCInternalError( + "jump2terminator: Missing secondary label for instruction {}" + .format(j)) + label_else = next_label + return BranchingTerminator(j.cond, j.op1, j.op2, j.label, label_else) + case AbsoluteJump(): + return AbsoluteJump(label=j.label) + case None: + if next_label: + return AbsoluteJump(next_label) + else: + return Return() diff --git a/MiniC/MiniCC.py b/MiniC/MiniCC.py index 4c785f2..d82787c 100644 --- a/MiniC/MiniCC.py +++ b/MiniC/MiniCC.py @@ -216,7 +216,14 @@ liveness file not found for {}.".format(form)) s = "{}.{}.exitssa.dot".format(basename, code.fdata.get_name()) print("CFG after SSA:", s) code.print_dot(s, view=True) - code.print_code(output, comment=comment) + from Lib.LinearCode import LinearCode # type: ignore[import] + if isinstance(code, LinearCode): + code.print_code(output, comment=comment) + else: + from Lib.CFG import CFG # type: ignore[import] + from TP04.LinearizeCFG import linearize # type: ignore[import] + assert (isinstance(code, CFG)) + code.print_code(output, linearize=linearize, comment=comment) if debug: visitor3.printSymbolTable() diff --git a/MiniC/TP04/BuildCFG.py b/MiniC/TP04/BuildCFG.py new file mode 100644 index 0000000..7e4d384 --- /dev/null +++ b/MiniC/TP04/BuildCFG.py @@ -0,0 +1,111 @@ +""" +CAP, CodeGeneration, CFG construction from linear code +""" + +from typing import List +from Lib.Errors import MiniCInternalError +from Lib.FunctionData import FunctionData +from Lib.LinearCode import LinearCode, CodeStatement +from Lib.Statement import ( + Instru3A, Comment, Label, AbsoluteJump, ConditionalJump +) +from Lib.Terminator import jump2terminator +from Lib.CFG import Block, BlockInstr, CFG + + +def find_leaders(instructions: List[CodeStatement]) -> List[int]: + """ + Find the leaders in the given list of instructions as linear code. + Returns a list of indices in the instruction list whose first is 0 and + last is len(instructions) + """ + leaders: List[int] = [0] + # TODO fill leaders (Lab4b, Exercise 3) + # The final "ret" is also a form of jump + leaders.append(len(instructions)) + return leaders + + +def separate_with_leaders(instructions: List[CodeStatement], + leaders: List[int]) -> List[List[CodeStatement]]: + """ + Partition the lists instructions into a list containing for + elements the lists of statements between indices + leaders[i] (included) and leaders[i+1] (excluded). + + If leaders[i] = leaders[i+1], do not add the empty list. + """ + chunks: List[List[CodeStatement]] = [] + for i in range(0, len(leaders)-1): + start = leaders[i] + end = leaders[i+1] + if start != end: + # Avoid corner-cases when a label immediately follows a jump + chunks.append(instructions[start:end]) + return chunks + + +def prepare_chunk(pre_chunk: List[CodeStatement], fdata: FunctionData) -> tuple[ + Label, ConditionalJump | AbsoluteJump | None, List[BlockInstr]]: + """ + Extract the potential label (respectively jump) + at the start (respectively end) of the list instrs_chunk, + and return the tuple with this label, this jump and the + rest of instrs_chunk. + + If there is no label at the start then return a fresh label instead, + thanks to fdata (use `fdata.fresh_label(fdata._name)` for instance). + If there is no jump at the end, return None instead. + + Raise an error if there is a label not in first position in pre_chunk, + or a jump not in last position. + """ + label = None + jump = None + inner_statements: List[CodeStatement] = pre_chunk + # Extract the first instruction from inner_statements if it is a label, or create a fresh one + raise NotImplementedError() # TODO (Lab4b, Exercise 3) + # Extract the last instruction from inner_statements if it is a jump, or do nothing + raise NotImplementedError() # TODO (Lab4b, Exercise 3) + # Check that there is no other label or jump left in inner_statements + l: List[BlockInstr] = [] + for i in inner_statements: + match i: + case AbsoluteJump() | ConditionalJump(): + raise MiniCInternalError( + "prepare_chunk: Jump {} not in last position of a chunk" + .format(i)) + case Label(): + raise MiniCInternalError( + "prepare_chunk: Label {} not in first position of a chunk" + .format(i)) + case Instru3A() | Comment(): + l.append(i) + return (label, jump, l) + + +def build_cfg(linCode: LinearCode) -> CFG: + """Extract the blocks from the linear code and add them to the CFG.""" + fdata = linCode.fdata + cfg = CFG(fdata) + instructions = linCode.get_instructions() + # 1. Identify Leaders + leaders = find_leaders(instructions) + # 2. Extract Chunks of Instructions + pre_chunks: List[List[CodeStatement]] = separate_with_leaders(instructions, leaders) + chunks: List[tuple[Label, ConditionalJump | AbsoluteJump | None, List[BlockInstr]]] = [ + prepare_chunk(pre_chunk, fdata) for pre_chunk in pre_chunks] + # 3. Build the Blocks + next_label = None + for (label, jump, block_instrs) in reversed(chunks): + term = jump2terminator(jump, next_label) + block = Block(label, block_instrs, term) + cfg.add_block(block) + next_label = label + # 4. Fill the edges + for block in cfg.get_blocks(): + for dest in cfg.out_blocks(block): + cfg.add_edge(block, dest) + # 5. Identify the entry label of the CFG + cfg.set_start(chunks[0][0]) + return cfg diff --git a/MiniC/TP04/LinearizeCFG.py b/MiniC/TP04/LinearizeCFG.py new file mode 100644 index 0000000..d3f185e --- /dev/null +++ b/MiniC/TP04/LinearizeCFG.py @@ -0,0 +1,43 @@ +""" +CAP, CodeGeneration, CFG linearization to a list of statements +""" + +from typing import List, Set + +from Lib.Statement import ( + Statement, AbsoluteJump, ConditionalJump +) +from Lib.Terminator import (Return, BranchingTerminator) +from Lib.CFG import Block + + +def ordered_blocks_list(cfg) -> List[Block]: + """ + Compute a list of blocks with optimized ordering for linearization. + """ + # TODO (Lab4b, Extension) + return cfg.get_blocks() + + +def linearize(cfg) -> List[Statement]: + """ + Linearize the given control flow graph as a list of instructions. + """ + # TODO (Lab 4b, Exercise 5) + l: List[Statement] = [] # Linearized CFG + blocks: List[Block] = ordered_blocks_list(cfg) + for j, block in enumerate(blocks): + # 1. Add the label of the block to the linearization + l.append(block.get_label()) + # 2. Add the body of the block to the linearization + l.extend(block.get_body()) + # 3. Add the terminator of the block to the linearization + match block.get_terminator(): + case BranchingTerminator() as j: + l.append(ConditionalJump(j.cond, j.op1, j.op2, j.label_then)) + l.append(AbsoluteJump(j.label_else)) + case AbsoluteJump() as j: + l.append(AbsoluteJump(j.label)) + case Return(): + l.append(AbsoluteJump(cfg.get_end())) + return l diff --git a/MiniC/TP04/tests/provided/dataflow/df00.c b/MiniC/TP04/tests/provided/dataflow/df00.c new file mode 100644 index 0000000..1ce157a --- /dev/null +++ b/MiniC/TP04/tests/provided/dataflow/df00.c @@ -0,0 +1,13 @@ +#include "printlib.h" + +int main() { + + int n,u; + n=6; + println_int(n); + + return 0; +} + +// EXPECTED +// 6 \ No newline at end of file diff --git a/MiniC/TP04/tests/provided/dataflow/df01.c b/MiniC/TP04/tests/provided/dataflow/df01.c new file mode 100644 index 0000000..acbf660 --- /dev/null +++ b/MiniC/TP04/tests/provided/dataflow/df01.c @@ -0,0 +1,15 @@ +#include "printlib.h" + +int main() { + + int n,u,v; + n=6; + u=12; + v=n+u; + println_int(v); + + return 0; +} + +// EXPECTED +// 18 \ No newline at end of file diff --git a/MiniC/TP04/tests/provided/dataflow/df02.c b/MiniC/TP04/tests/provided/dataflow/df02.c new file mode 100644 index 0000000..55282c9 --- /dev/null +++ b/MiniC/TP04/tests/provided/dataflow/df02.c @@ -0,0 +1,14 @@ +#include "printlib.h" + +int main() { + + int n,v; + bool u; + n=6; + u=12>n; + println_bool(1 1) + { + n = n - 1; + u = u + n; + } + println_int(u); + return 0; +} + +// EXPECTED +// 15 \ No newline at end of file diff --git a/MiniC/TP04/tests/provided/dataflow/df04.c b/MiniC/TP04/tests/provided/dataflow/df04.c new file mode 100644 index 0000000..638a3a8 --- /dev/null +++ b/MiniC/TP04/tests/provided/dataflow/df04.c @@ -0,0 +1,16 @@ +#include "printlib.h" + +int main() +{ + + int x, y; + x = 2; + if (x < 4) + x = 4; + else + x = 5; + + return 0; +} + +// EXPECTED \ No newline at end of file diff --git a/MiniC/TP04/tests/provided/dataflow/df05.c b/MiniC/TP04/tests/provided/dataflow/df05.c new file mode 100644 index 0000000..ccee7fb --- /dev/null +++ b/MiniC/TP04/tests/provided/dataflow/df05.c @@ -0,0 +1,14 @@ +#include "printlib.h" + +int main() +{ + int x; + x = 0; + while (x < 4) + { + x = x + 1; + } + return 0; +} + +// EXPECTED \ No newline at end of file diff --git a/MiniC/TP04/tp4b.pdf b/MiniC/TP04/tp4b.pdf new file mode 100644 index 0000000..17317de Binary files /dev/null and b/MiniC/TP04/tp4b.pdf differ diff --git a/PLANNING.md b/PLANNING.md index 0e51daf..8fd5186 100644 --- a/PLANNING.md +++ b/PLANNING.md @@ -60,7 +60,7 @@ _Academic first semester 2022-2023_ # Week 5: -- :hammer: Lab 3: Wednesday 05/10/2021, 10h15-12h15. Rooms A1 (Nicolas Chappe) & B2 (Rémi Di Guardia) +- :hammer: Lab 4a: Wednesday 05/10/2021, 10h15-12h15. Rooms A1 (Nicolas Chappe) & B2 (Rémi Di Guardia) * Syntax directed code generation [TP04](MiniC/TP04/tp4.pdf). * Code in [MiniC/TP04/](MiniC/TP04/). @@ -69,3 +69,16 @@ _Academic first semester 2022-2023_ - :book: 5th Course session: Friday 70/10/2022, 10:15. Amphi B (Gabriel Radanne) * CFG [slides in english](course/capmif_cours06_irs.pdf). + +# Week 6: + +- :hammer: Lab 4b: Wednesday 12/10/2021, 10h15-12h15. Rooms A1 (Nicolas Chappe) & B2 (Rémi Di Guardia) + + * Control Flow Graph [TP04b](MiniC/TP04/tp4b.pdf). + * Code in [MiniC/TP04/](MiniC/TP04/). + * Documentation [here](docs/index.html). + +- :book: 7th Course session: Friday 14/10/2022, 10:15. Amphi B (Gabriel Radanne) + + * SSA [slides in english](course/cap_cours06a_ssa.pdf). + diff --git a/course/cap_cours06a_ssa.pdf b/course/cap_cours06a_ssa.pdf new file mode 100644 index 0000000..ff024bd Binary files /dev/null and b/course/cap_cours06a_ssa.pdf differ diff --git a/docs/_modules/Lib/Allocator.html b/docs/_modules/Lib/Allocator.html index 12dfaea..741511f 100644 --- a/docs/_modules/Lib/Allocator.html +++ b/docs/_modules/Lib/Allocator.html @@ -44,6 +44,8 @@
  • Base library - Function data
  • Linear intermediate representation
  • Temporary allocation
  • +
  • Control Flow Graph - CFG and Basic blocks
  • +
  • Control Flow Graph - Terminators
  • diff --git a/docs/_modules/Lib/CFG.html b/docs/_modules/Lib/CFG.html new file mode 100644 index 0000000..bc7a4cd --- /dev/null +++ b/docs/_modules/Lib/CFG.html @@ -0,0 +1,377 @@ + + + + + + Lib.CFG — MiniC documentation + + + + + + + + + + + + + + + +
    + + +
    + +
    +
    +
    + +
    +
    +
    +
    + +

    Source code for Lib.CFG

    +"""
    +Classes for a RiscV CFG: :py:class:`CFG` for the CFG itself,
    +and :py:class:`Block` for its basic blocks.
    +"""
    +
    +from graphviz import Digraph  # for dot output
    +from typing import cast, Any, Dict, List, Set, Iterator
    +
    +from Lib.Errors import MiniCInternalError
    +from Lib.Operands import (Operand, Immediate, Function, A0)
    +from Lib.Statement import (
    +    Statement, Instru3A, Label,
    +    AbsoluteJump, ConditionalJump, Comment
    +)
    +from Lib.Terminator import (
    +    Terminator, BranchingTerminator, Return)
    +from Lib.FunctionData import (FunctionData, _iter_statements, _print_code)
    +
    +
    +BlockInstr = Instru3A | Comment
    +
    +
    +
    [docs]class Block: + """ + A basic block of a :py:class:`CFG` is made of three main parts: + + - a start :py:class:`label <Lib.Statement.Label>` that uniquely identifies the block in the CFG + - the main body of the block, a list of instructions + (excluding labels, jumps and branching instructions) + - a :py:class:`terminator <Lib.Terminator.Terminator>` + that represents the final jump or branching instruction of the block, + and points to the successors of the block. + See the documentation for :py:class:`Lib.Terminator.Terminator` for further explanations. + """ + + _terminator: Terminator + _label: Label + _phis: List[Statement] + _instructions: List[BlockInstr] + _in: List['Block'] + _gen: Set + _kill: Set + + def __init__(self, label: Label, insts: List[BlockInstr], terminator: Terminator): + self._label = label + self._instructions = insts + self._in = [] + self._phis = [] + self._terminator = terminator + self._gen = set() + self._kill = set() + + def __str__(self): + instr = [i for i in self._instructions if not isinstance(i, Comment)] + instr_str = '\n'.join(map(str, instr)) + s = '{}:\n\n{}'.format(self._label, instr_str) + return s + +
    [docs] def to_dot(self) -> str: # pragma: no cover + """Outputs all statements of the block as a string.""" + # dot is weird: lines ending with \l instead of \n are left-aligned. + NEWLINE = '\\l ' + instr = [] + instr += self._phis + instr += [i for i in self._instructions if not isinstance(i, Comment)] + instr += [self.get_terminator()] + instr_str = NEWLINE.join(map(str, instr)) + s = '{}:{}{}\\l'.format(self._label, NEWLINE, instr_str) + return s
    + + def __repr__(self): + return str(self._label) + +
    [docs] def get_body(self) -> List[BlockInstr]: + """Return the statements in the body of the block (no phi-node nor the terminator).""" + return self._instructions
    + +
    [docs] def get_all_statements(self) -> List[Statement]: + """ + Return all statements of the block + (including phi-nodes and the terminator, but not the label of the block). + """ + return (self._phis + + cast(List[Statement], self._instructions) + + [self.get_terminator()])
    + +
    [docs] def get_label(self) -> Label: + """Return the label of the block.""" + return self._label
    + +
    [docs] def get_in(self) -> List['Block']: + """Return the list of blocks with an edge to the considered block.""" + return self._in
    + +
    [docs] def get_terminator(self) -> Terminator: + """Return the terminator of the block.""" + return self._terminator
    + +
    [docs] def set_terminator(self, term: Terminator) -> None: + """Set the terminator of the block.""" + self._terminator = term
    + +
    [docs] def iter_statements(self, f) -> None: + """Iterate over instructions. + For each real instruction i (not label or comment), replace it + with the list of instructions given by f(i). + + Assume there is no phi-node. + """ + assert (self._phis == []) + new_statements = _iter_statements(self._instructions, f) + end_statements = f(self.get_terminator()) + if len(end_statements) >= 1 and isinstance(end_statements[-1], Terminator): + new_terminator = end_statements.pop(-1) + self._instructions = new_statements + end_statements + self.set_terminator(new_terminator) + else: + raise MiniCInternalError( + "Block.iter_statements: Invalid replacement for terminator {}:\n {}" + .format(self.get_terminator(), end_statements))
    + +
    [docs] def add_instruction(self, instr: BlockInstr) -> None: + """Add an instruction to the body of the block.""" + self._instructions.append(instr)
    + + +
    [docs]class CFG: + """ + A complete control-flow graph representing a function. + This class is mainly made of a list of basic :py:class:`Block`, + a label indicating the :py:meth:`entry point of the function <get_start>`, + and an :py:meth:`exit label <get_end>`. + + As with linear code, metadata about the function can be found + in the :py:attr:`fdata` member variable. + """ + + _start: Label + _end: Label + _blocks: Dict[Label, Block] + + #: Metadata about the function represented by this CFG + fdata: FunctionData + + def __init__(self, fdata: FunctionData): + self._blocks = {} + self.fdata = fdata + self._init_blks() + self._end = self.fdata.fresh_label("end") + + def _init_blks(self) -> None: + """Add a block for division by 0.""" + # Label for the address of the error message + # This address is added by print_code + label_div_by_zero_msg = Label(self.fdata._label_div_by_zero.name + "_msg") + blk = Block(self.fdata._label_div_by_zero, [ + Instru3A("la", A0, label_div_by_zero_msg), + Instru3A("call", Function("println_string")), + Instru3A("li", A0, Immediate(1)), + Instru3A("call", Function("exit")), + ], terminator=Return()) + self.add_block(blk) + +
    [docs] def get_start(self) -> Label: + """Return the entry label of the CFG.""" + return self._start
    + +
    [docs] def set_start(self, start: Label) -> None: + """Set the entry label of the CFG.""" + assert (start in self._blocks) + self._start = start
    + +
    [docs] def get_end(self) -> Label: + """Return the exit label of the CFG.""" + return self._end
    + +
    [docs] def add_block(self, blk: Block) -> None: + """Add a new block to the CFG.""" + self._blocks[blk._label] = blk
    + +
    [docs] def get_block(self, name: Label) -> Block: + """Return the block with label `name`.""" + return self._blocks[name]
    + +
    [docs] def get_blocks(self) -> List[Block]: + """Return all the blocks.""" + return [b for b in self._blocks.values()]
    + +
    [docs] def get_entries(self) -> List[Block]: + """Return all the blocks with no predecessors.""" + return [b for b in self._blocks.values() if not b.get_in()]
    + +
    [docs] def add_edge(self, src: Block, dest: Block) -> None: + """Add the edge src -> dest in the control flow graph.""" + dest.get_in().append(src)
    + # assert (dest.get_label() in src.get_terminator().targets()) + +
    [docs] def remove_edge(self, src: Block, dest: Block) -> None: + """Remove the edge src -> dest in the control flow graph.""" + dest.get_in().remove(src)
    + # assert (dest.get_label() not in src.get_terminator().targets()) + +
    [docs] def out_blocks(self, block: Block) -> List[Block]: + """ + Return the list of blocks in the CFG targeted by + the Terminator of Block block. + """ + return [self.get_block(dest) for dest in block.get_terminator().targets()]
    + +
    [docs] def gather_defs(self) -> Dict[Any, Set[Block]]: + """ + Return a dictionary associating variables to all the blocks + containing one of their definitions. + """ + defs: Dict[Operand, Set[Block]] = dict() + for b in self.get_blocks(): + for i in b.get_all_statements(): + for v in i.defined(): + if v not in defs: + defs[v] = {b} + else: + defs[v].add(b) + return defs
    + +
    [docs] def iter_statements(self, f) -> None: + """Apply f to all instructions in all the blocks.""" + for b in self.get_blocks(): + b.iter_statements(f)
    + +
    [docs] def linearize_naive(self) -> Iterator[Statement]: + """ + Linearize the given control flow graph as a list of instructions. + Naive procedure that adds jumps everywhere. + """ + for label, block in self._blocks.items(): + yield label + for i in block._instructions: + yield i + match block.get_terminator(): + case BranchingTerminator() as j: + # In case of conditional jump, add the missing edge + yield ConditionalJump(j.cond, j.op1, j.op2, j.label_then) + yield AbsoluteJump(j.label_else) + case AbsoluteJump() as j: + yield AbsoluteJump(j.label) + case Return(): + yield AbsoluteJump(self.get_end())
    + +
    [docs] def print_code(self, output, linearize=(lambda cfg: list(cfg.linearize_naive())), + comment=None) -> None: + """Print the linearization of the CFG.""" + statements = linearize(self) + _print_code(statements, self.fdata, output, init_label=self._start, + fin_label=self._end, fin_div0=False, comment=comment)
    + +
    [docs] def print_dot(self, filename, DF=None, view=False) -> None: # pragma: no cover + """Print the CFG as a graph.""" + graph = Digraph() + # nodes + for name, blk in self._blocks.items(): + if DF is not None: + print(str(name), blk._label) + df_str = "{}" if blk not in DF or not len(DF[blk]) else str(DF[blk]) + df_lab = blk.to_dot() + "\n\nDominance frontier:\n" + df_str + else: + df_lab = blk.to_dot() + graph.node(str(blk._label), label=df_lab, shape='rectangle') + # edges + for name, blk in self._blocks.items(): + for child in blk.get_terminator().targets(): + graph.edge(str(blk._label), str(child)) + graph.render(filename, view=view)
    +
    + +
    +
    + +
    +
    +
    +
    + + + + \ No newline at end of file diff --git a/docs/_modules/Lib/Errors.html b/docs/_modules/Lib/Errors.html index 12c3e32..0d94674 100644 --- a/docs/_modules/Lib/Errors.html +++ b/docs/_modules/Lib/Errors.html @@ -44,6 +44,8 @@
  • Base library - Function data
  • Linear intermediate representation
  • Temporary allocation
  • +
  • Control Flow Graph - CFG and Basic blocks
  • +
  • Control Flow Graph - Terminators
  • diff --git a/docs/_modules/Lib/FunctionData.html b/docs/_modules/Lib/FunctionData.html index 580bce4..5a19688 100644 --- a/docs/_modules/Lib/FunctionData.html +++ b/docs/_modules/Lib/FunctionData.html @@ -44,6 +44,8 @@
  • Base library - Function data
  • Linear intermediate representation
  • Temporary allocation
  • +
  • Control Flow Graph - CFG and Basic blocks
  • +
  • Control Flow Graph - Terminators
  • diff --git a/docs/_modules/Lib/LinearCode.html b/docs/_modules/Lib/LinearCode.html index a385f23..cc350fe 100644 --- a/docs/_modules/Lib/LinearCode.html +++ b/docs/_modules/Lib/LinearCode.html @@ -44,6 +44,8 @@
  • Base library - Function data
  • Linear intermediate representation
  • Temporary allocation
  • +
  • Control Flow Graph - CFG and Basic blocks
  • +
  • Control Flow Graph - Terminators
  • diff --git a/docs/_modules/Lib/Operands.html b/docs/_modules/Lib/Operands.html index 4770337..1fcfa16 100644 --- a/docs/_modules/Lib/Operands.html +++ b/docs/_modules/Lib/Operands.html @@ -44,6 +44,8 @@
  • Base library - Function data
  • Linear intermediate representation
  • Temporary allocation
  • +
  • Control Flow Graph - CFG and Basic blocks
  • +
  • Control Flow Graph - Terminators
  • @@ -195,19 +197,33 @@ # Shortcuts for registers in RISCV # Only integer registers + +#: Zero register ZERO = Register(0) +#: RA = Register(1) +#: SP = Register(2) +#: GP = Register(3) # Register not used for this course +#: TP = Register(4) # Register not used for this course +#: A = tuple(Register(i + 10) for i in range(8)) +#: S = tuple(Register(i + 8) for i in range(2)) + tuple(Register(i + 18) for i in range(10)) +#: T = tuple(Register(i + 5) for i in range(3)) + tuple(Register(i + 28) for i in range(4)) + + +#: A0 = A[0] # function args/return Values: A0, A1 +#: A1 = A[1] +#: FP = S[0] # Frame Pointer = Saved register 0 -# General purpose registers, usable for the allocator +#: General purpose registers, usable for the allocator GP_REGS = S[4:] + T # s0, s1, s2 and s3 are special diff --git a/docs/_modules/Lib/RiscV.html b/docs/_modules/Lib/RiscV.html index a844dc3..6f37f75 100644 --- a/docs/_modules/Lib/RiscV.html +++ b/docs/_modules/Lib/RiscV.html @@ -44,6 +44,8 @@
  • Base library - Function data
  • Linear intermediate representation
  • Temporary allocation
  • +
  • Control Flow Graph - CFG and Basic blocks
  • +
  • Control Flow Graph - Terminators
  • diff --git a/docs/_modules/Lib/Statement.html b/docs/_modules/Lib/Statement.html index e23cd76..3160cdb 100644 --- a/docs/_modules/Lib/Statement.html +++ b/docs/_modules/Lib/Statement.html @@ -44,6 +44,8 @@
  • Base library - Function data
  • Linear intermediate representation
  • Temporary allocation
  • +
  • Control Flow Graph - CFG and Basic blocks
  • +
  • Control Flow Graph - Terminators
  • @@ -100,9 +102,11 @@ """A Statement, which is an instruction, a comment or a label."""
    [docs] def defined(self) -> List[Operand]: + """Operands defined (written) in this instruction""" return []
    [docs] def used(self) -> List[Operand]: + """Operands used (read) in this instruction""" return []
    [docs] def substitute(self: TStatement, subst: Dict[Operand, Operand]) -> TStatement: @@ -112,7 +116,7 @@
    [docs] def printIns(self, stream): """ - Print the statement on the output. + Print the statement on the given output. Should never be called on the base class. """ raise NotImplementedError
    @@ -163,6 +167,7 @@ raise NotImplementedError
    [docs] def args(self) -> List[Operand]: + """List of operands the instruction takes""" raise NotImplementedError
    [docs] def defined(self): @@ -194,7 +199,7 @@ return hash((self.ins, *self.args()))
    [docs] def printIns(self, stream): - """Print the instruction on the output.""" + """Print the instruction on the given output.""" print(' ', str(self), file=stream)
    diff --git a/docs/_modules/Lib/Terminator.html b/docs/_modules/Lib/Terminator.html new file mode 100644 index 0000000..8cd4e5f --- /dev/null +++ b/docs/_modules/Lib/Terminator.html @@ -0,0 +1,238 @@ + + + + + + Lib.Terminator — MiniC documentation + + + + + + + + + + + + + + + +
    + + +
    + +
    +
    +
    + +
    +
    +
    +
    + +

    Source code for Lib.Terminator

    +"""
    +MIF08, CAP, CFG library - Terminators.
    +
    +Each :py:class:`block <Lib.CFG.Block>` of a :py:class:`CFG <Lib.CFG.CFG>`
    +ends with a branching instruction called a terminator.
    +There are three kinds of terminators:
    +
    +- :py:class:`Lib.Statement.AbsoluteJump` is a non-conditional jump
    +  to another block of the CFG
    +- :py:class:`BranchingTerminator` is a conditional branching
    +  instruction with two successor blocks.
    +  Unlike the class :py:class:`ConditionalJump <Lib.Statement.ConditionalJump>`
    +  that was used in :py:class:`LinearCode <Lib.LinearCode.LinearCode>`,
    +  both successor labels have to be specified.
    +- :py:class:`Return` marks the end of the function
    +
    +During the construction of the CFG, :py:func:`jump2terminator` builds
    +a terminator for each extracted chunk of instructions.
    +"""
    +
    +from dataclasses import dataclass
    +from typing import List, Dict
    +from Lib.Errors import MiniCInternalError
    +from Lib.Operands import Operand, Renamer, Temporary, Condition
    +from Lib.Statement import AbsoluteJump, ConditionalJump, Instruction, Label, Statement
    +
    +
    +
    [docs]@dataclass(unsafe_hash=True) +class Return(Statement): + """A terminator that marks the end of the function.""" + + def __str__(self): + return ("return") + +
    [docs] def printIns(self, stream): + print("return", file=stream)
    + +
    [docs] def targets(self) -> List[Label]: + """Return the labels targetted by the Return terminator.""" + return []
    + +
    [docs] def args(self) -> List[Operand]: + return []
    + +
    [docs] def rename(self, renamer: Renamer): + pass
    + +
    [docs] def substitute(self, subst: Dict[Operand, Operand]): + if subst != {}: + raise Exception( + "substitute: No possible substitution on instruction {}" + .format(self)) + return self
    + + +
    [docs]@dataclass(init=False) +class BranchingTerminator(Instruction): + """A terminating statement with a condition.""" + + #: The condition of the branch + cond: Condition + #: The destination label if the condition is true + label_then: Label + #: The destination label if the condition is false + label_else: Label + #: The first operand of the condition + op1: Operand + #: The second operand of the condition + op2: Operand + _read_only = True + + def __init__(self, cond: Condition, op1: Operand, op2: Operand, + label_then: Label, label_else: Label): + self.cond = cond + self.label_then = label_then + self.label_else = label_else + self.op1 = op1 + self.op2 = op2 + self.ins = str(self.cond) + +
    [docs] def args(self) -> List[Operand]: + return [self.op1, self.op2, self.label_then, self.label_else]
    + +
    [docs] def targets(self) -> List[Label]: + """Return the labels targetted by the Branching terminator.""" + return [self.label_then, self.label_else]
    + +
    [docs] def rename(self, renamer: Renamer): + if isinstance(self.op1, Temporary): + self.op1 = renamer.replace(self.op1) + if isinstance(self.op2, Temporary): + self.op2 = renamer.replace(self.op2)
    + +
    [docs] def substitute(self, subst: Dict[Operand, Operand]): + for op in subst: + if op not in self.args(): + raise Exception( + "substitute: Operand {} is not present in instruction {}" + .format(op, self)) + op1 = subst.get(self.op1, self.op1) if isinstance(self.op1, Temporary) \ + else self.op1 + op2 = subst.get(self.op2, self.op2) if isinstance(self.op2, Temporary) \ + else self.op2 + return BranchingTerminator(self.cond, op1, op2, self.label_then, self.label_else)
    + + def __hash__(self): + return hash(super)
    + + +Terminator = Return | AbsoluteJump | BranchingTerminator + + +
    [docs]def jump2terminator(j: ConditionalJump | AbsoluteJump | None, + next_label: Label | None) -> Terminator: + """ + Construct the Terminator associated to the potential jump j + to the potential label next_label. + """ + match j: + case ConditionalJump(): + if (next_label is None): + raise MiniCInternalError( + "jump2terminator: Missing secondary label for instruction {}" + .format(j)) + label_else = next_label + return BranchingTerminator(j.cond, j.op1, j.op2, j.label, label_else) + case AbsoluteJump(): + return AbsoluteJump(label=j.label) + case None: + if next_label: + return AbsoluteJump(next_label) + else: + return Return()
    +
    + +
    +
    + +
    +
    +
    +
    + + + + \ No newline at end of file diff --git a/docs/_modules/index.html b/docs/_modules/index.html index 030026f..4dc1e00 100644 --- a/docs/_modules/index.html +++ b/docs/_modules/index.html @@ -44,6 +44,8 @@
  • Base library - Function data
  • Linear intermediate representation
  • Temporary allocation
  • +
  • Control Flow Graph - CFG and Basic blocks
  • +
  • Control Flow Graph - Terminators
  • @@ -71,12 +73,14 @@

    All modules for which code is available

    diff --git a/docs/_sources/api/Lib.CFG.rst.txt b/docs/_sources/api/Lib.CFG.rst.txt new file mode 100644 index 0000000..f66acb6 --- /dev/null +++ b/docs/_sources/api/Lib.CFG.rst.txt @@ -0,0 +1,7 @@ +Lib.CFG module +============== + +.. automodule:: Lib.CFG + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/_sources/api/Lib.Terminator.rst.txt b/docs/_sources/api/Lib.Terminator.rst.txt new file mode 100644 index 0000000..47a406c --- /dev/null +++ b/docs/_sources/api/Lib.Terminator.rst.txt @@ -0,0 +1,7 @@ +Lib.Terminator module +===================== + +.. automodule:: Lib.Terminator + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/_sources/api/Lib.rst.txt b/docs/_sources/api/Lib.rst.txt index 9de53f0..ed2c967 100644 --- a/docs/_sources/api/Lib.rst.txt +++ b/docs/_sources/api/Lib.rst.txt @@ -8,12 +8,14 @@ Submodules :maxdepth: 4 Lib.Allocator + Lib.CFG Lib.Errors Lib.FunctionData Lib.LinearCode Lib.Operands Lib.RiscV Lib.Statement + Lib.Terminator Module contents --------------- diff --git a/docs/_sources/index.rst.txt b/docs/_sources/index.rst.txt index 839de3a..5514932 100644 --- a/docs/_sources/index.rst.txt +++ b/docs/_sources/index.rst.txt @@ -17,6 +17,8 @@ Welcome to MiniC's documentation! Base library - Function data Linear intermediate representation Temporary allocation + Control Flow Graph - CFG and Basic blocks + Control Flow Graph - Terminators These pages document the various Python sources in the Lib/ folder of MiniC. You should not have to edit them *at all*. @@ -46,6 +48,12 @@ Temporary allocation Before implementing the all-in-memory allocator of lab 4a, you should understand the naive allocator in the :doc:`api/Lib.Allocator`. +Control Flow Graph Intermediate representation +---------------------------------------------- + +The classes for the CFG and its basic blocks are in the :doc:`api/Lib.CFG`. +Each block ends with a terminator, as documented in the :doc:`api/Lib.Terminator`. + Indices and tables ================== diff --git a/docs/api/Lib.Allocator.html b/docs/api/Lib.Allocator.html index 733254d..0691e4d 100644 --- a/docs/api/Lib.Allocator.html +++ b/docs/api/Lib.Allocator.html @@ -19,6 +19,7 @@ + @@ -46,6 +47,8 @@
  • Base library - Function data
  • Linear intermediate representation
  • Temporary allocation
  • +
  • Control Flow Graph - CFG and Basic blocks
  • +
  • Control Flow Graph - Terminators
  • @@ -121,6 +124,12 @@ registers or memory locations.

    Bases: Allocator

    Naive Allocator: try to assign a register to each temporary, fails if there are more temporaries than registers.

    +
    +
    +replace(old_instr: Instruction) List[Instruction][source]
    +

    Replace Temporary operands with the corresponding allocated Register.

    +
    +
    prepare() None[source]
    @@ -128,12 +137,6 @@ fails if there are more temporaries than registers.

    Fail if there are too many temporaries.

    -
    -
    -replace(old_instr: Instruction) List[Instruction][source]
    -

    Replace Temporary operands with the corresponding allocated Register.

    -
    - @@ -143,6 +146,7 @@ Fail if there are too many temporaries.