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..644881d
Binary files /dev/null and b/MiniC/TP04/tp4b.pdf differ
diff --git a/PLANNING.md b/PLANNING.md
index 0e51daf..7826ca7 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,11 @@ _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 5:
+
+- :hammer: Lab 4b: Wednesday 12/10/2021, 10h15-12h15. Rooms A1 (Nicolas Chappe) & B2 (Rémi Di Guardia)
+
+ * Syntax directed code generation [TP04b](MiniC/TP04/tp4b.pdf).
+ * Code in [MiniC/TP04/](MiniC/TP04/).
+ * Documentation [here](docs/index.html).
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 @@
+"""
+Classes for a RiscV CFG: :py:class:`CFG` for the CFG itself,
+and :py:class:`Block` for its basic blocks.
+"""
+
+fromgraphvizimportDigraph# for dot output
+fromtypingimportcast,Any,Dict,List,Set,Iterator
+
+fromLib.ErrorsimportMiniCInternalError
+fromLib.Operandsimport(Operand,Immediate,Function,A0)
+fromLib.Statementimport(
+ Statement,Instru3A,Label,
+ AbsoluteJump,ConditionalJump,Comment
+)
+fromLib.Terminatorimport(
+ Terminator,BranchingTerminator,Return)
+fromLib.FunctionDataimport(FunctionData,_iter_statements,_print_code)
+
+
+BlockInstr=Instru3A|Comment
+
+
+
[docs]classBlock:
+ """
+ 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=[iforiinself._instructionsifnotisinstance(i,Comment)]
+ instr_str='\n'.join(map(str,instr))
+ s='{}:\n\n{}'.format(self._label,instr_str)
+ returns
+
+
[docs]defto_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+=[iforiinself._instructionsifnotisinstance(i,Comment)]
+ instr+=[self.get_terminator()]
+ instr_str=NEWLINE.join(map(str,instr))
+ s='{}:{}{}\\l'.format(self._label,NEWLINE,instr_str)
+ returns
[docs]defget_body(self)->List[BlockInstr]:
+ """Return the statements in the body of the block (no phi-node nor the terminator)."""
+ returnself._instructions
+
+
[docs]defget_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]defget_label(self)->Label:
+ """Return the label of the block."""
+ returnself._label
+
+
[docs]defget_in(self)->List['Block']:
+ """Return the list of blocks with an edge to the considered block."""
+ returnself._in
+
+
[docs]defget_terminator(self)->Terminator:
+ """Return the terminator of the block."""
+ returnself._terminator
+
+
[docs]defset_terminator(self,term:Terminator)->None:
+ """Set the terminator of the block."""
+ self._terminator=term
+
+
[docs]defiter_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())
+ iflen(end_statements)>=1andisinstance(end_statements[-1],Terminator):
+ new_terminator=end_statements.pop(-1)
+ self._instructions=new_statements+end_statements
+ self.set_terminator(new_terminator)
+ else:
+ raiseMiniCInternalError(
+ "Block.iter_statements: Invalid replacement for terminator {}:\n{}"
+ .format(self.get_terminator(),end_statements))
+
+
[docs]defadd_instruction(self,instr:BlockInstr)->None:
+ """Add an instruction to the body of the block."""
+ self._instructions.append(instr)
+
+
+
[docs]classCFG:
+ """
+ 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]defget_start(self)->Label:
+ """Return the entry label of the CFG."""
+ returnself._start
+
+
[docs]defset_start(self,start:Label)->None:
+ """Set the entry label of the CFG."""
+ assert(startinself._blocks)
+ self._start=start
+
+
[docs]defget_end(self)->Label:
+ """Return the exit label of the CFG."""
+ returnself._end
+
+
[docs]defadd_block(self,blk:Block)->None:
+ """Add a new block to the CFG."""
+ self._blocks[blk._label]=blk
+
+
[docs]defget_block(self,name:Label)->Block:
+ """Return the block with label `name`."""
+ returnself._blocks[name]
+
+
[docs]defget_blocks(self)->List[Block]:
+ """Return all the blocks."""
+ return[bforbinself._blocks.values()]
+
+
[docs]defget_entries(self)->List[Block]:
+ """Return all the blocks with no predecessors."""
+ return[bforbinself._blocks.values()ifnotb.get_in()]
+
+
[docs]defadd_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]defremove_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]defout_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)fordestinblock.get_terminator().targets()]
+
+
[docs]defgather_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()
+ forbinself.get_blocks():
+ foriinb.get_all_statements():
+ forvini.defined():
+ ifvnotindefs:
+ defs[v]={b}
+ else:
+ defs[v].add(b)
+ returndefs
+
+
[docs]defiter_statements(self,f)->None:
+ """Apply f to all instructions in all the blocks."""
+ forbinself.get_blocks():
+ b.iter_statements(f)
+
+
[docs]deflinearize_naive(self)->Iterator[Statement]:
+ """
+ Linearize the given control flow graph as a list of instructions.
+ Naive procedure that adds jumps everywhere.
+ """
+ forlabel,blockinself._blocks.items():
+ yieldlabel
+ foriinblock._instructions:
+ yieldi
+ matchblock.get_terminator():
+ caseBranchingTerminator()asj:
+ # In case of conditional jump, add the missing edge
+ yieldConditionalJump(j.cond,j.op1,j.op2,j.label_then)
+ yieldAbsoluteJump(j.label_else)
+ caseAbsoluteJump()asj:
+ yieldAbsoluteJump(j.label)
+ caseReturn():
+ yieldAbsoluteJump(self.get_end())
+
+
[docs]defprint_code(self,output,linearize=(lambdacfg: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)
@@ -195,19 +197,33 @@
# Shortcuts for registers in RISCV# Only integer registers
+
+#: Zero registerZERO=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)foriinrange(8))
+#:S=tuple(Register(i+8)foriinrange(2))+tuple(Register(i+18)foriinrange(10))
+#:T=tuple(Register(i+5)foriinrange(3))+tuple(Register(i+28)foriinrange(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 allocatorGP_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 @@
[docs]defprintIns(self,stream):"""
- Print the statement on the output.
+ Print the statement on the given output. Should never be called on the base class. """raiseNotImplementedError
@@ -163,6 +167,7 @@
raiseNotImplementedError
[docs]defargs(self)->List[Operand]:
+ """List of operands the instruction takes"""raiseNotImplementedError
[docs]defprintIns(self,stream):
- """Print the instruction on the output."""
+ """Print the instruction on the given output."""print(' ',str(self),file=stream)
+"""
+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.
+"""
+
+fromdataclassesimportdataclass
+fromtypingimportList,Dict
+fromLib.ErrorsimportMiniCInternalError
+fromLib.OperandsimportOperand,Renamer,Temporary,Condition
+fromLib.StatementimportAbsoluteJump,ConditionalJump,Instruction,Label,Statement
+
+
+
[docs]@dataclass(unsafe_hash=True)
+classReturn(Statement):
+ """A terminator that marks the end of the function."""
+
+ def__str__(self):
+ return("return")
+
+
[docs]defsubstitute(self,subst:Dict[Operand,Operand]):
+ ifsubst!={}:
+ raiseException(
+ "substitute: No possible substitution on instruction {}"
+ .format(self))
+ returnself
+
+
+
[docs]@dataclass(init=False)
+classBranchingTerminator(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)
+
+
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 @@