Merge remote-tracking branch 'origin/main'

This commit is contained in:
2022-10-16 23:26:50 +02:00
45 changed files with 2485 additions and 329 deletions
+272
View File
@@ -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 <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
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 <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)
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)
+133
View File
@@ -0,0 +1,133 @@
"""
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
@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()
+8 -1
View File
@@ -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()
+111
View File
@@ -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
+43
View File
@@ -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
+13
View File
@@ -0,0 +1,13 @@
#include "printlib.h"
int main() {
int n,u;
n=6;
println_int(n);
return 0;
}
// EXPECTED
// 6
+15
View File
@@ -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
+14
View File
@@ -0,0 +1,14 @@
#include "printlib.h"
int main() {
int n,v;
bool u;
n=6;
u=12>n;
println_bool(1<n && u);
return 0;
}
// EXPECTED
// 1
+18
View File
@@ -0,0 +1,18 @@
#include "printlib.h"
int main()
{
int n, u, v;
n = 6;
u = 0;
while (n > 1)
{
n = n - 1;
u = u + n;
}
println_int(u);
return 0;
}
// EXPECTED
// 15
+16
View File
@@ -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
+14
View File
@@ -0,0 +1,14 @@
#include "printlib.h"
int main()
{
int x;
x = 0;
while (x < 4)
{
x = x + 1;
}
return 0;
}
// EXPECTED
Binary file not shown.