diff --git a/PLANNING.md b/PLANNING.md index 5d37fba..c31327e 100644 --- a/PLANNING.md +++ b/PLANNING.md @@ -64,3 +64,10 @@ _Academic first semester 2024-2025_ * SSA [slides in english](course/capmif_cours06a_ssa.pdf). +# Week 6: + +- :hammer: Lab 4b: Thursday 14/10/2024, 13h30-15h30. Room E001 (Samuel Humeau & Emma Nardino) + + * Control Flow Graph [TP04b](TP04/tp4b.pdf). + * Code in [MiniC/TP04/](MiniC/TP04/). + * Documentation (updated) [here](docs/html/index.html). diff --git a/TP04/tp4b.pdf b/TP04/tp4b.pdf new file mode 100644 index 0000000..bf6de3f Binary files /dev/null and b/TP04/tp4b.pdf differ diff --git a/docs/html/_modules/Lib/Allocator.html b/docs/html/_modules/Lib/Allocator.html index 988ec40..07b9b12 100644 --- a/docs/html/_modules/Lib/Allocator.html +++ b/docs/html/_modules/Lib/Allocator.html @@ -47,6 +47,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/html/_modules/Lib/CFG.html b/docs/html/_modules/Lib/CFG.html new file mode 100644 index 0000000..c9a0886 --- /dev/null +++ b/docs/html/_modules/Lib/CFG.html @@ -0,0 +1,403 @@ + + + + + + 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_body_and_terminator(self) -> List[Statement]: + """ + Return all statements of the block, except phi-nodes + (and the label of the block). + """ + return (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 get_phis(self) -> List[Statement]: + """Return the list of all φ instructions of the block.""" + return self._phis
    + +
    [docs] def add_phi(self, phi: Statement) -> None: + """Add a φ instruction to the block.""" + self._phis.append(phi)
    + +
    [docs] def set_phis(self, phis: List[Statement]) -> None: + """Replace the φ instructions in the block by the given list `phis`.""" + self._phis = phis
    + +
    [docs] def remove_all_phis(self) -> None: + """Remove all φ instructions in the block.""" + self._phis = []
    + +
    [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: + 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/html/_modules/Lib/Errors.html b/docs/html/_modules/Lib/Errors.html index 3664bb0..6d661b7 100644 --- a/docs/html/_modules/Lib/Errors.html +++ b/docs/html/_modules/Lib/Errors.html @@ -47,6 +47,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/html/_modules/Lib/FunctionData.html b/docs/html/_modules/Lib/FunctionData.html index a0ccdb2..c0e5908 100644 --- a/docs/html/_modules/Lib/FunctionData.html +++ b/docs/html/_modules/Lib/FunctionData.html @@ -47,6 +47,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/html/_modules/Lib/LinearCode.html b/docs/html/_modules/Lib/LinearCode.html index 85c1c47..af2b72f 100644 --- a/docs/html/_modules/Lib/LinearCode.html +++ b/docs/html/_modules/Lib/LinearCode.html @@ -47,6 +47,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/html/_modules/Lib/Operands.html b/docs/html/_modules/Lib/Operands.html index 15133cc..befdd1f 100644 --- a/docs/html/_modules/Lib/Operands.html +++ b/docs/html/_modules/Lib/Operands.html @@ -47,6 +47,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/html/_modules/Lib/RiscV.html b/docs/html/_modules/Lib/RiscV.html index ca45afb..9ecc4f4 100644 --- a/docs/html/_modules/Lib/RiscV.html +++ b/docs/html/_modules/Lib/RiscV.html @@ -47,6 +47,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/html/_modules/Lib/Statement.html b/docs/html/_modules/Lib/Statement.html index d64c780..66469b0 100644 --- a/docs/html/_modules/Lib/Statement.html +++ b/docs/html/_modules/Lib/Statement.html @@ -47,6 +47,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/html/_modules/Lib/Terminator.html b/docs/html/_modules/Lib/Terminator.html new file mode 100644 index 0000000..80942ff --- /dev/null +++ b/docs/html/_modules/Lib/Terminator.html @@ -0,0 +1,261 @@ + + + + + + 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] def with_args(self, new_args: List[Operand]): + if new_args != []: + raise Exception( + "substitute: No possible substitution on instruction {}" + .format(self)) + return self
    + +
    [docs] def is_read_only(self) -> bool: + return True
    + + +
    [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)
    + +
    [docs] def with_args(self, new_args: List[Operand]): + if len(new_args) != 4: + raise Exception( + "substitute: Invalid number of arguments for instruction {}, expected 4 got {}" + .format(self, new_args)) + op1 = new_args[0] + op2 = new_args[1] + return BranchingTerminator(self.cond, op1, op2, self.label_then, self.label_else)
    + + def __hash__(self): + return hash(super)
    + + +Terminator = Return | AbsoluteJump | BranchingTerminator +"""Type alias for terminators""" + + +
    [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 _: + if next_label: + return AbsoluteJump(next_label) + else: + return Return()
    +
    + +
    +
    + +
    +
    +
    +
    + + + + \ No newline at end of file diff --git a/docs/html/_modules/index.html b/docs/html/_modules/index.html index f394ee2..2c66b33 100644 --- a/docs/html/_modules/index.html +++ b/docs/html/_modules/index.html @@ -47,6 +47,8 @@
  • Base library - Function data
  • Linear intermediate representation
  • Temporary allocation
  • +
  • Control Flow Graph - CFG and Basic blocks
  • +
  • Control Flow Graph - Terminators
  • @@ -74,12 +76,14 @@

    All modules for which code is available

    diff --git a/docs/html/_sources/api/Lib.CFG.rst.txt b/docs/html/_sources/api/Lib.CFG.rst.txt new file mode 100644 index 0000000..f66acb6 --- /dev/null +++ b/docs/html/_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/html/_sources/api/Lib.Terminator.rst.txt b/docs/html/_sources/api/Lib.Terminator.rst.txt new file mode 100644 index 0000000..47a406c --- /dev/null +++ b/docs/html/_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/html/_sources/api/Lib.rst.txt b/docs/html/_sources/api/Lib.rst.txt index 9de53f0..ed2c967 100644 --- a/docs/html/_sources/api/Lib.rst.txt +++ b/docs/html/_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/html/api/Lib.Allocator.html b/docs/html/api/Lib.Allocator.html index 7dfcdf7..eac9a05 100644 --- a/docs/html/api/Lib.Allocator.html +++ b/docs/html/api/Lib.Allocator.html @@ -18,6 +18,7 @@ + @@ -62,6 +63,8 @@ +
  • Control Flow Graph - CFG and Basic blocks
  • +
  • Control Flow Graph - Terminators
  • @@ -159,6 +162,7 @@ Fail if there are too many temporaries.